BluetoothLeAdvertiser.java revision 8e5270fdf5639461d67e9a898a85520abac6053d
1/* 2 * Copyright (C) 2014 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 17package android.bluetooth.le; 18 19import android.bluetooth.BluetoothAdapter; 20import android.bluetooth.BluetoothGatt; 21import android.bluetooth.IBluetoothGatt; 22import android.bluetooth.IBluetoothGattCallback; 23import android.bluetooth.IBluetoothManager; 24import android.os.Handler; 25import android.os.Looper; 26import android.os.ParcelUuid; 27import android.os.RemoteException; 28import android.util.Log; 29 30import java.util.HashMap; 31import java.util.List; 32import java.util.Map; 33import java.util.UUID; 34 35/** 36 * This class provides a way to perform Bluetooth LE advertise operations, such as start and stop 37 * advertising. An advertiser can broadcast up to 31 bytes of advertisement data represented by 38 * {@link AdvertisementData}. 39 * <p> 40 * To get an instance of {@link BluetoothLeAdvertiser}, call the 41 * {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method. 42 * <p> 43 * Note most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN} 44 * permission. 45 * 46 * @see AdvertisementData 47 */ 48public final class BluetoothLeAdvertiser { 49 50 private static final String TAG = "BluetoothLeAdvertiser"; 51 52 private final IBluetoothManager mBluetoothManager; 53 private final Handler mHandler; 54 private BluetoothAdapter mBluetoothAdapter; 55 private final Map<AdvertiseCallback, AdvertiseCallbackWrapper> 56 mLeAdvertisers = new HashMap<AdvertiseCallback, AdvertiseCallbackWrapper>(); 57 58 /** 59 * Use BluetoothAdapter.getLeAdvertiser() instead. 60 * 61 * @param bluetoothManager 62 * @hide 63 */ 64 public BluetoothLeAdvertiser(IBluetoothManager bluetoothManager) { 65 mBluetoothManager = bluetoothManager; 66 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 67 mHandler = new Handler(Looper.getMainLooper()); 68 } 69 70 /** 71 * Start Bluetooth LE Advertising. The {@code advertiseData} would be broadcasted after the 72 * operation succeeds. Returns immediately, the operation status are delivered through 73 * {@code callback}. 74 * <p> 75 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 76 * 77 * @param settings Settings for Bluetooth LE advertising. 78 * @param advertiseData Advertisement data to be broadcasted. 79 * @param callback Callback for advertising status. 80 */ 81 public void startAdvertising(AdvertiseSettings settings, 82 AdvertisementData advertiseData, final AdvertiseCallback callback) { 83 startAdvertising(settings, advertiseData, null, callback); 84 } 85 86 /** 87 * Start Bluetooth LE Advertising. The {@code advertiseData} would be broadcasted after the 88 * operation succeeds. The {@code scanResponse} would be returned when the scanning device sends 89 * active scan request. Method returns immediately, the operation status are delivered through 90 * {@code callback}. 91 * <p> 92 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 93 * 94 * @param settings Settings for Bluetooth LE advertising. 95 * @param advertiseData Advertisement data to be advertised in advertisement packet. 96 * @param scanResponse Scan response associated with the advertisement data. 97 * @param callback Callback for advertising status. 98 */ 99 public void startAdvertising(AdvertiseSettings settings, 100 AdvertisementData advertiseData, AdvertisementData scanResponse, 101 final AdvertiseCallback callback) { 102 if (callback == null) { 103 throw new IllegalArgumentException("callback cannot be null"); 104 } 105 if (mLeAdvertisers.containsKey(callback)) { 106 postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED); 107 return; 108 } 109 IBluetoothGatt gatt; 110 try { 111 gatt = mBluetoothManager.getBluetoothGatt(); 112 } catch (RemoteException e) { 113 Log.e(TAG, "failed to get bluetooth gatt - ", e); 114 postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_CONTROLLER_FAILURE); 115 return; 116 } 117 if (!mBluetoothAdapter.isMultipleAdvertisementSupported()) { 118 postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED); 119 return; 120 } 121 AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData, 122 scanResponse, settings, gatt); 123 UUID uuid = UUID.randomUUID(); 124 try { 125 gatt.registerClient(new ParcelUuid(uuid), wrapper); 126 if (wrapper.advertiseStarted()) { 127 mLeAdvertisers.put(callback, wrapper); 128 } 129 } catch (RemoteException e) { 130 Log.e(TAG, "failed to stop advertising", e); 131 } 132 } 133 134 /** 135 * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in 136 * {@link BluetoothLeAdvertiser#startAdvertising}. 137 * <p> 138 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 139 * 140 * @param callback {@link AdvertiseCallback} for delivering stopping advertising status. 141 */ 142 public void stopAdvertising(final AdvertiseCallback callback) { 143 if (callback == null) { 144 throw new IllegalArgumentException("callback cannot be null"); 145 } 146 AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(callback); 147 if (wrapper == null) { 148 postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_NOT_STARTED); 149 return; 150 } 151 try { 152 IBluetoothGatt gatt = mBluetoothManager.getBluetoothGatt(); 153 if (gatt == null) { 154 postCallbackFailure(callback, 155 AdvertiseCallback.ADVERTISE_FAILED_GATT_SERVICE_FAILURE); 156 } 157 gatt.stopMultiAdvertising(wrapper.mLeHandle); 158 if (wrapper.advertiseStopped()) { 159 mLeAdvertisers.remove(callback); 160 } 161 } catch (RemoteException e) { 162 Log.e(TAG, "failed to stop advertising", e); 163 postCallbackFailure(callback, 164 AdvertiseCallback.ADVERTISE_FAILED_GATT_SERVICE_FAILURE); 165 } 166 } 167 168 /** 169 * Bluetooth GATT interface callbacks for advertising. 170 */ 171 private static class AdvertiseCallbackWrapper extends IBluetoothGattCallback.Stub { 172 private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000; 173 private final AdvertiseCallback mAdvertiseCallback; 174 private final AdvertisementData mAdvertisement; 175 private final AdvertisementData mScanResponse; 176 private final AdvertiseSettings mSettings; 177 private final IBluetoothGatt mBluetoothGatt; 178 179 // mLeHandle 0: not registered 180 // -1: scan stopped 181 // >0: registered and scan started 182 private int mLeHandle; 183 private boolean isAdvertising = false; 184 185 public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback, 186 AdvertisementData advertiseData, AdvertisementData scanResponse, 187 AdvertiseSettings settings, 188 IBluetoothGatt bluetoothGatt) { 189 mAdvertiseCallback = advertiseCallback; 190 mAdvertisement = advertiseData; 191 mScanResponse = scanResponse; 192 mSettings = settings; 193 mBluetoothGatt = bluetoothGatt; 194 mLeHandle = 0; 195 } 196 197 public boolean advertiseStarted() { 198 boolean started = false; 199 synchronized (this) { 200 if (mLeHandle == -1) { 201 return false; 202 } 203 try { 204 wait(LE_CALLBACK_TIMEOUT_MILLIS); 205 } catch (InterruptedException e) { 206 Log.e(TAG, "Callback reg wait interrupted: ", e); 207 } 208 started = (mLeHandle > 0 && isAdvertising); 209 } 210 return started; 211 } 212 213 public boolean advertiseStopped() { 214 synchronized (this) { 215 try { 216 wait(LE_CALLBACK_TIMEOUT_MILLIS); 217 } catch (InterruptedException e) { 218 Log.e(TAG, "Callback reg wait interrupted: " + e); 219 } 220 return !isAdvertising; 221 } 222 } 223 224 /** 225 * Application interface registered - app is ready to go 226 */ 227 @Override 228 public void onClientRegistered(int status, int clientIf) { 229 Log.d(TAG, "onClientRegistered() - status=" + status + " clientIf=" + clientIf); 230 synchronized (this) { 231 if (status == BluetoothGatt.GATT_SUCCESS) { 232 mLeHandle = clientIf; 233 try { 234 mBluetoothGatt.startMultiAdvertising(mLeHandle, mAdvertisement, 235 mScanResponse, mSettings); 236 } catch (RemoteException e) { 237 Log.e(TAG, "fail to start le advertise: " + e); 238 mLeHandle = -1; 239 notifyAll(); 240 } 241 } else { 242 // registration failed 243 mLeHandle = -1; 244 notifyAll(); 245 } 246 } 247 } 248 249 @Override 250 public void onClientConnectionState(int status, int clientIf, 251 boolean connected, String address) { 252 // no op 253 } 254 255 @Override 256 public void onScanResult(String address, int rssi, byte[] advData) { 257 // no op 258 } 259 260 @Override 261 public void onGetService(String address, int srvcType, 262 int srvcInstId, ParcelUuid srvcUuid) { 263 // no op 264 } 265 266 @Override 267 public void onGetIncludedService(String address, int srvcType, 268 int srvcInstId, ParcelUuid srvcUuid, 269 int inclSrvcType, int inclSrvcInstId, 270 ParcelUuid inclSrvcUuid) { 271 // no op 272 } 273 274 @Override 275 public void onGetCharacteristic(String address, int srvcType, 276 int srvcInstId, ParcelUuid srvcUuid, 277 int charInstId, ParcelUuid charUuid, 278 int charProps) { 279 // no op 280 } 281 282 @Override 283 public void onGetDescriptor(String address, int srvcType, 284 int srvcInstId, ParcelUuid srvcUuid, 285 int charInstId, ParcelUuid charUuid, 286 int descInstId, ParcelUuid descUuid) { 287 // no op 288 } 289 290 @Override 291 public void onSearchComplete(String address, int status) { 292 // no op 293 } 294 295 @Override 296 public void onCharacteristicRead(String address, int status, int srvcType, 297 int srvcInstId, ParcelUuid srvcUuid, 298 int charInstId, ParcelUuid charUuid, byte[] value) { 299 // no op 300 } 301 302 @Override 303 public void onCharacteristicWrite(String address, int status, int srvcType, 304 int srvcInstId, ParcelUuid srvcUuid, 305 int charInstId, ParcelUuid charUuid) { 306 // no op 307 } 308 309 @Override 310 public void onNotify(String address, int srvcType, 311 int srvcInstId, ParcelUuid srvcUuid, 312 int charInstId, ParcelUuid charUuid, 313 byte[] value) { 314 // no op 315 } 316 317 @Override 318 public void onDescriptorRead(String address, int status, int srvcType, 319 int srvcInstId, ParcelUuid srvcUuid, 320 int charInstId, ParcelUuid charUuid, 321 int descInstId, ParcelUuid descrUuid, byte[] value) { 322 // no op 323 } 324 325 @Override 326 public void onDescriptorWrite(String address, int status, int srvcType, 327 int srvcInstId, ParcelUuid srvcUuid, 328 int charInstId, ParcelUuid charUuid, 329 int descInstId, ParcelUuid descrUuid) { 330 // no op 331 } 332 333 @Override 334 public void onExecuteWrite(String address, int status) { 335 // no op 336 } 337 338 @Override 339 public void onReadRemoteRssi(String address, int rssi, int status) { 340 // no op 341 } 342 343 @Override 344 public void onAdvertiseStateChange(int advertiseState, int status) { 345 // no op 346 } 347 348 @Override 349 public void onMultiAdvertiseCallback(int status) { 350 synchronized (this) { 351 if (status == 0) { 352 isAdvertising = !isAdvertising; 353 if (!isAdvertising) { 354 try { 355 mBluetoothGatt.unregisterClient(mLeHandle); 356 mLeHandle = -1; 357 } catch (RemoteException e) { 358 Log.e(TAG, "remote exception when unregistering", e); 359 } 360 } 361 mAdvertiseCallback.onSuccess(null); 362 } else { 363 mAdvertiseCallback.onFailure(status); 364 } 365 notifyAll(); 366 } 367 368 } 369 370 /** 371 * Callback reporting LE ATT MTU. 372 * 373 * @hide 374 */ 375 @Override 376 public void onConfigureMTU(String address, int mtu, int status) { 377 // no op 378 } 379 380 @Override 381 public void onConnectionCongested(String address, boolean congested) { 382 // no op 383 } 384 385 @Override 386 public void onBatchScanResults(List<ScanResult> results) { 387 // no op 388 } 389 } 390 391 private void postCallbackFailure(final AdvertiseCallback callback, final int error) { 392 mHandler.post(new Runnable() { 393 @Override 394 public void run() { 395 callback.onFailure(error); 396 } 397 }); 398 } 399} 400