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