• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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 static com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assume.assumeTrue;
22 import static org.mockito.Mockito.mock;
23 
24 import android.bluetooth.le.AdvertiseCallback;
25 import android.bluetooth.le.AdvertiseData;
26 import android.bluetooth.le.AdvertiseSettings;
27 import android.bluetooth.le.BluetoothLeAdvertiser;
28 import android.bluetooth.le.BluetoothLeScanner;
29 import android.bluetooth.le.ScanCallback;
30 import android.bluetooth.le.ScanFilter;
31 import android.bluetooth.le.ScanResult;
32 import android.bluetooth.le.ScanSettings;
33 import android.bluetooth.test_utils.BlockingBluetoothAdapter;
34 import android.bluetooth.test_utils.TestUtils;
35 import android.content.Context;
36 import android.os.ParcelUuid;
37 import android.platform.test.annotations.RequiresFlagsDisabled;
38 import android.platform.test.annotations.RequiresFlagsEnabled;
39 import android.platform.test.flag.junit.CheckFlagsRule;
40 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
41 import android.util.Log;
42 
43 import androidx.test.core.app.ApplicationProvider;
44 import androidx.test.ext.junit.runners.AndroidJUnit4;
45 
46 import com.android.bluetooth.flags.Flags;
47 import com.android.compatibility.common.util.AdoptShellPermissionsRule;
48 
49 import org.junit.After;
50 import org.junit.Before;
51 import org.junit.Rule;
52 import org.junit.Test;
53 import org.junit.runner.RunWith;
54 
55 import pandora.HostProto;
56 
57 import java.util.ArrayList;
58 import java.util.List;
59 import java.util.concurrent.CompletableFuture;
60 import java.util.concurrent.TimeUnit;
61 
62 @RunWith(AndroidJUnit4.class)
63 public class BleOnStateTest {
64     private static final String TAG = BleOnStateTest.class.getSimpleName();
65 
66     private static final int TIMEOUT_ADVERTISING_MS = 1000;
67     private static final int TIMEOUT_SCANNING_MS = 2000;
68     private static final String TEST_UUID_STRING = "00001805-0000-1000-8000-00805f9b34fb";
69 
70     @Rule(order = 0)
71     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
72 
73     @Rule(order = 1)
74     public final AdoptShellPermissionsRule mPermissionRule = new AdoptShellPermissionsRule();
75 
76     @Rule(order = 2)
77     public final PandoraDevice mBumble = new PandoraDevice();
78 
79     private final Context mContext = ApplicationProvider.getApplicationContext();
80     private final BluetoothManager mManager = mContext.getSystemService(BluetoothManager.class);
81     private final BluetoothAdapter mAdapter = mManager.getAdapter();
82     private final BluetoothLeScanner mLeScanner = mAdapter.getBluetoothLeScanner();
83 
84     private boolean mWasBluetoothAdapterEnabled = true;
85 
86     @Before
setUp()87     public void setUp() {
88         assumeTrue(TestUtils.hasBluetooth());
89         mWasBluetoothAdapterEnabled = mAdapter.isEnabled();
90         if (mWasBluetoothAdapterEnabled) {
91             assertThat(BlockingBluetoothAdapter.disable(true)).isTrue();
92         }
93         assertThat(BlockingBluetoothAdapter.enableBLE(true)).isTrue();
94     }
95 
96     @After
tearDown()97     public void tearDown() {
98         assumeTrue(TestUtils.hasBluetooth());
99         assertThat(BlockingBluetoothAdapter.disableBLE()).isTrue();
100         if (mWasBluetoothAdapterEnabled) {
101             assertThat(BlockingBluetoothAdapter.enable()).isTrue();
102         }
103     }
104 
105     @Test
confirm_stateIsBleOn()106     public void confirm_stateIsBleOn() {
107         assertThat(mAdapter.isEnabled()).isFalse();
108         assertThat(mAdapter.isLeEnabled()).isTrue();
109     }
110 
111     @Test
whenOnlyStartScanDuringBleOnOffOrOn_scanWorks()112     public void whenOnlyStartScanDuringBleOnOffOrOn_scanWorks() {
113         advertiseWithBumble(TEST_UUID_STRING, HostProto.OwnAddressType.PUBLIC);
114 
115         ScanFilter scanFilter =
116                 new ScanFilter.Builder()
117                         .setServiceUuid(ParcelUuid.fromString(TEST_UUID_STRING))
118                         .build();
119 
120         List<ScanResult> results =
121                 startScanning(
122                         scanFilter, ScanSettings.CALLBACK_TYPE_ALL_MATCHES, /* isLegacy= */ true);
123 
124         assertThat(results).isNotNull();
125         assertThat(results.get(0).getScanRecord().getServiceUuids().get(0))
126                 .isEqualTo(ParcelUuid.fromString(TEST_UUID_STRING));
127         assertThat(results.get(1).getScanRecord().getServiceUuids().get(0))
128                 .isEqualTo(ParcelUuid.fromString(TEST_UUID_STRING));
129     }
130 
131     @Test
132     @RequiresFlagsDisabled(Flags.FLAG_ONLY_START_SCAN_DURING_BLE_ON)
whenOnlyStartScanDuringBleOnOff_canAdvertise()133     public void whenOnlyStartScanDuringBleOnOff_canAdvertise() throws Exception {
134         final BluetoothLeAdvertiser bluetoothLeAdvertiser = mAdapter.getBluetoothLeAdvertiser();
135 
136         AdvertiseSettings settings = new AdvertiseSettings.Builder().build();
137         AdvertiseData advertiseData = new AdvertiseData.Builder().build();
138 
139         final CompletableFuture<Integer> future = new CompletableFuture<>();
140 
141         AdvertiseCallback advertiseCallback =
142                 new AdvertiseCallback() {
143                     @Override
144                     public void onStartSuccess(AdvertiseSettings settingsInEffect) {
145                         future.complete(AdvertiseCallback.ADVERTISE_SUCCESS);
146                     }
147 
148                     @Override
149                     public void onStartFailure(int errorCode) {
150                         future.complete(errorCode);
151                     }
152                 };
153 
154         try {
155             bluetoothLeAdvertiser.startAdvertising(settings, advertiseData, advertiseCallback);
156             future.completeOnTimeout(null, TIMEOUT_ADVERTISING_MS, TimeUnit.MILLISECONDS).join();
157 
158             Integer advertisingResult = future.get();
159             assertThat(advertisingResult).isNotNull();
160             assertThat(advertisingResult).isEqualTo(AdvertiseCallback.ADVERTISE_SUCCESS);
161         } finally {
162             bluetoothLeAdvertiser.stopAdvertising(advertiseCallback);
163         }
164     }
165 
166     @Test
167     @RequiresFlagsEnabled(Flags.FLAG_ONLY_START_SCAN_DURING_BLE_ON)
whenOnlyStartScanDuringBleOnOn_cantAdvertise()168     public void whenOnlyStartScanDuringBleOnOn_cantAdvertise() throws Exception {
169         final BluetoothLeAdvertiser bluetoothLeAdvertiser = mAdapter.getBluetoothLeAdvertiser();
170 
171         AdvertiseSettings settings = new AdvertiseSettings.Builder().build();
172         AdvertiseData advertiseData = new AdvertiseData.Builder().build();
173 
174         final CompletableFuture<Integer> future = new CompletableFuture<>();
175 
176         AdvertiseCallback advertiseCallback =
177                 new AdvertiseCallback() {
178                     @Override
179                     public void onStartSuccess(AdvertiseSettings settingsInEffect) {
180                         future.complete(AdvertiseCallback.ADVERTISE_SUCCESS);
181                     }
182 
183                     @Override
184                     public void onStartFailure(int errorCode) {
185                         future.complete(errorCode);
186                     }
187                 };
188 
189         try {
190             bluetoothLeAdvertiser.startAdvertising(settings, advertiseData, advertiseCallback);
191             future.completeOnTimeout(null, TIMEOUT_ADVERTISING_MS, TimeUnit.MILLISECONDS).join();
192 
193             Integer advertisingResult = future.get();
194             assertThat(advertisingResult).isNotNull();
195             assertThat(advertisingResult)
196                     .isEqualTo(AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
197         } finally {
198             bluetoothLeAdvertiser.stopAdvertising(advertiseCallback);
199         }
200     }
201 
202     @Test
203     @RequiresFlagsDisabled(Flags.FLAG_ONLY_START_SCAN_DURING_BLE_ON)
whenOnlyStartScanDuringBleOnOff_gattCanConnect()204     public void whenOnlyStartScanDuringBleOnOff_gattCanConnect() {
205         advertiseWithBumble();
206 
207         BluetoothDevice device =
208                 mAdapter.getRemoteLeDevice(
209                         Utils.BUMBLE_RANDOM_ADDRESS, BluetoothDevice.ADDRESS_TYPE_RANDOM);
210 
211         BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class);
212         BluetoothGatt gatt = device.connectGatt(mContext, false, gattCallback);
213         assertThat(gatt).isNotNull();
214         gatt.close();
215     }
216 
217     @Test
218     @RequiresFlagsEnabled(Flags.FLAG_ONLY_START_SCAN_DURING_BLE_ON)
whenOnlyStartScanDuringBleOnOn_gattCantConnect()219     public void whenOnlyStartScanDuringBleOnOn_gattCantConnect() {
220         advertiseWithBumble();
221 
222         BluetoothDevice device =
223                 mAdapter.getRemoteLeDevice(
224                         Utils.BUMBLE_RANDOM_ADDRESS, BluetoothDevice.ADDRESS_TYPE_RANDOM);
225 
226         BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class);
227         BluetoothGatt gatt = device.connectGatt(mContext, false, gattCallback);
228         assertThat(gatt).isNull();
229     }
230 
advertiseWithBumble()231     private void advertiseWithBumble() {
232         HostProto.AdvertiseRequest request =
233                 HostProto.AdvertiseRequest.newBuilder()
234                         .setLegacy(true)
235                         .setConnectable(true)
236                         .setOwnAddressType(HostProto.OwnAddressType.RANDOM)
237                         .build();
238 
239         StreamObserverSpliterator<HostProto.AdvertiseResponse> responseObserver =
240                 new StreamObserverSpliterator<>();
241 
242         mBumble.host().advertise(request, responseObserver);
243     }
244 
advertiseWithBumble(String serviceUuid, HostProto.OwnAddressType addressType)245     private void advertiseWithBumble(String serviceUuid, HostProto.OwnAddressType addressType) {
246         HostProto.AdvertiseRequest.Builder requestBuilder =
247                 HostProto.AdvertiseRequest.newBuilder().setOwnAddressType(addressType);
248 
249         if (serviceUuid != null) {
250             HostProto.DataTypes.Builder dataTypeBuilder = HostProto.DataTypes.newBuilder();
251             dataTypeBuilder.addCompleteServiceClassUuids128(serviceUuid);
252             requestBuilder.setData(dataTypeBuilder.build());
253         }
254 
255         advertiseWithBumble(requestBuilder, true);
256     }
257 
advertiseWithBumble( HostProto.AdvertiseRequest.Builder requestBuilder, boolean isLegacy)258     private void advertiseWithBumble(
259             HostProto.AdvertiseRequest.Builder requestBuilder, boolean isLegacy) {
260         requestBuilder.setLegacy(isLegacy);
261         // Collect and ignore responses.
262         StreamObserverSpliterator<HostProto.AdvertiseResponse> responseObserver =
263                 new StreamObserverSpliterator<>();
264         mBumble.host().advertise(requestBuilder.build(), responseObserver);
265     }
266 
startScanning( ScanFilter scanFilter, int callbackType, boolean isLegacy)267     private List<ScanResult> startScanning(
268             ScanFilter scanFilter, int callbackType, boolean isLegacy) {
269         CompletableFuture<List<ScanResult>> future = new CompletableFuture<>();
270         List<ScanResult> scanResults = new ArrayList<>();
271 
272         ScanSettings scanSettings =
273                 new ScanSettings.Builder()
274                         .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
275                         .setCallbackType(callbackType)
276                         .setLegacy(isLegacy)
277                         .build();
278 
279         ScanCallback scanCallback =
280                 new ScanCallback() {
281                     @Override
282                     public void onScanResult(int callbackType, ScanResult result) {
283                         Log.i(
284                                 TAG,
285                                 "onScanResult "
286                                         + "address: "
287                                         + result.getDevice().getAddress()
288                                         + ", connectable: "
289                                         + result.isConnectable()
290                                         + ", callbackType: "
291                                         + callbackType
292                                         + ", service uuids: "
293                                         + result.getScanRecord().getServiceUuids());
294 
295                         scanResults.add(result);
296                         if (callbackType != ScanSettings.CALLBACK_TYPE_ALL_MATCHES
297                                 || scanResults.size() > 1) {
298                             future.complete(scanResults);
299                         }
300                     }
301 
302                     @Override
303                     public void onScanFailed(int errorCode) {
304                         Log.i(TAG, "onScanFailed " + "errorCode: " + errorCode);
305                         future.complete(null);
306                     }
307                 };
308 
309         mLeScanner.startScan(List.of(scanFilter), scanSettings, scanCallback);
310 
311         List<ScanResult> result =
312                 future.completeOnTimeout(null, TIMEOUT_SCANNING_MS, TimeUnit.MILLISECONDS).join();
313 
314         mLeScanner.stopScan(scanCallback);
315 
316         return result;
317     }
318 }
319