1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.bluetooth
18 
19 import android.bluetooth.DckTestRule.LeScanResult
20 import android.bluetooth.le.ScanFilter
21 import android.bluetooth.le.ScanSettings
22 import android.content.Context
23 import android.os.ParcelUuid
24 import androidx.test.core.app.ApplicationProvider
25 import com.android.compatibility.common.util.AdoptShellPermissionsRule
26 import com.google.common.truth.Truth.assertThat
27 import com.google.testing.junit.testparameterinjector.TestParameter
28 import com.google.testing.junit.testparameterinjector.TestParameterInjector
29 import java.util.UUID
30 import kotlinx.coroutines.flow.first
31 import kotlinx.coroutines.runBlocking
32 import kotlinx.coroutines.withTimeout
33 import org.junit.Assume.assumeTrue
34 import org.junit.Rule
35 import org.junit.Test
36 import org.junit.runner.RunWith
37 
38 /** DCK LE Scan Tests */
39 @RunWith(TestParameterInjector::class)
40 class DckScanTest(
41     private @TestParameter val isBluetoothToggled: Boolean,
42     private @TestParameter val isRemoteAdvertisingWithUuid: Boolean,
43     private @TestParameter val isGattConnected: Boolean
44 ) {
45     // TODO(315852141): Include variations for LE only vs. Dual mode Bumble when supported
46     // TODO(315852141): Include variations for two advertisements at the same time
47     // TODO(303502437): Include variations for other callback types when supported in rootcanal
48 
49     private val context: Context = ApplicationProvider.getApplicationContext()
50 
51     // Gives shell permissions during the test.
52     @Rule(order = 0) @JvmField val shellPermissionRule = AdoptShellPermissionsRule()
53 
54     // Setup a Bumble Pandora device for the duration of the test.
55     // Acting as a Pandora client, it can be interacted with through the Pandora APIs.
56     @Rule(order = 1) @JvmField val bumble = PandoraDevice()
57 
58     // Test rule for common DCK test setup and teardown procedures, along with utility APIs.
59     @get:Rule(order = 2)
60     public val dck =
61         DckTestRule(
62             context,
63             bumble,
64             isBluetoothToggled = isBluetoothToggled,
65             isRemoteAdvertisingWithUuid = isRemoteAdvertisingWithUuid,
66             isGattConnected = isGattConnected
67         )
68 
69     @Test
scanForIrkAndIdentityAddress_remoteFoundnull70     fun scanForIrkAndIdentityAddress_remoteFound() {
71         // TODO(316001793): Retrieve identity address from Bumble
72         val scanFilter =
73             ScanFilter.Builder()
74                 .setDeviceAddress(
75                     TEST_ADDRESS_RANDOM_STATIC,
76                     BluetoothDevice.ADDRESS_TYPE_RANDOM,
77                     Utils.BUMBLE_IRK
78                 )
79                 .build()
80         val scanSettings =
81             ScanSettings.Builder()
82                 .setScanMode(ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY)
83                 .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
84                 .setMatchMode(ScanSettings.MATCH_MODE_STICKY)
85                 .build()
86 
87         val result: LeScanResult = runBlocking {
88             withTimeout(TIMEOUT_MS) { dck.scanWithPendingIntent(scanFilter, scanSettings).first() }
89         }
90 
91         assertThat(result).isInstanceOf(LeScanResult.Success::class.java)
92         assertThat((result as LeScanResult.Success).callbackType)
93             .isEqualTo(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
94         assertThat((result as LeScanResult.Success).scanResult.device.address)
95             .isEqualTo(TEST_ADDRESS_RANDOM_STATIC)
96     }
97 
98     @Test
scanForUuid_remoteFoundnull99     fun scanForUuid_remoteFound() {
100         // Assume isRemoteAdvertisingWithUuid is true to skip tests in which
101         // device is not advertising with UUID
102         assumeTrue(isRemoteAdvertisingWithUuid)
103         val scanFilter = ScanFilter.Builder().setServiceUuid(ParcelUuid(CCC_DK_UUID)).build()
104         val scanSettings =
105             ScanSettings.Builder()
106                 .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
107                 .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
108                 .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
109                 .build()
110 
111         val result: LeScanResult = runBlocking {
112             withTimeout(TIMEOUT_MS) { dck.scanWithCallback(scanFilter, scanSettings).first() }
113         }
114 
115         assertThat(result).isInstanceOf(LeScanResult.Success::class.java)
116         assertThat((result as LeScanResult.Success).callbackType)
117             .isEqualTo(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
118         assertThat((result as LeScanResult.Success).scanResult.device.address)
119             .isEqualTo(Utils.BUMBLE_RANDOM_ADDRESS)
120     }
121 
122     companion object {
123         private const val TIMEOUT_MS = 3000L
124         private const val TEST_ADDRESS_RANDOM_STATIC = "F0:43:A8:23:10:11"
125         private val CCC_DK_UUID = UUID.fromString("0000FFF5-0000-1000-8000-00805f9b34fb")
126     }
127 }
128