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