BluetoothLeAdvertiser.java revision 35cd4c853f748e295352bda708b26ee39544d06d
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.BluetoothGattCallbackWrapper; 22import android.bluetooth.BluetoothUuid; 23import android.bluetooth.IBluetoothGatt; 24import android.bluetooth.IBluetoothManager; 25import android.os.Handler; 26import android.os.Looper; 27import android.os.ParcelUuid; 28import android.os.RemoteException; 29import android.util.Log; 30 31import java.util.HashMap; 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 static final int MAX_ADVERTISING_DATA_BYTES = 31; 53 // Each fields need one byte for field length and another byte for field type. 54 private static final int OVERHEAD_BYTES_PER_FIELD = 2; 55 // Flags field will be set by system. 56 private static final int FLAGS_FIELD_BYTES = 3; 57 private static final int MANUFACTURER_SPECIFIC_DATA_LENGTH = 2; 58 private static final int SERVICE_DATA_UUID_LENGTH = 2; 59 60 private final IBluetoothManager mBluetoothManager; 61 private final Handler mHandler; 62 private BluetoothAdapter mBluetoothAdapter; 63 private final Map<AdvertiseCallback, AdvertiseCallbackWrapper> 64 mLeAdvertisers = new HashMap<AdvertiseCallback, AdvertiseCallbackWrapper>(); 65 66 /** 67 * Use BluetoothAdapter.getLeAdvertiser() instead. 68 * 69 * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management 70 * @hide 71 */ 72 public BluetoothLeAdvertiser(IBluetoothManager bluetoothManager) { 73 mBluetoothManager = bluetoothManager; 74 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 75 mHandler = new Handler(Looper.getMainLooper()); 76 } 77 78 /** 79 * Start Bluetooth LE Advertising. On success, the {@code advertiseData} will be broadcasted. 80 * Returns immediately, the operation status is delivered through {@code callback}. 81 * <p> 82 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 83 * 84 * @param settings Settings for Bluetooth LE advertising. 85 * @param advertiseData Advertisement data to be broadcasted. 86 * @param callback Callback for advertising status. 87 */ 88 public void startAdvertising(AdvertiseSettings settings, 89 AdvertiseData advertiseData, final AdvertiseCallback callback) { 90 startAdvertising(settings, advertiseData, null, callback); 91 } 92 93 /** 94 * Start Bluetooth LE Advertising. The {@code advertiseData} will be broadcasted if the 95 * operation succeeds. The {@code scanResponse} is returned when a scanning device sends an 96 * active scan request. This method returns immediately, the operation status is delivered 97 * through {@code callback}. 98 * <p> 99 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 100 * 101 * @param settings Settings for Bluetooth LE advertising. 102 * @param advertiseData Advertisement data to be advertised in advertisement packet. 103 * @param scanResponse Scan response associated with the advertisement data. 104 * @param callback Callback for advertising status. 105 */ 106 public void startAdvertising(AdvertiseSettings settings, 107 AdvertiseData advertiseData, AdvertiseData scanResponse, 108 final AdvertiseCallback callback) { 109 synchronized (mLeAdvertisers) { 110 BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); 111 if (callback == null) { 112 throw new IllegalArgumentException("callback cannot be null"); 113 } 114 if (!mBluetoothAdapter.isMultipleAdvertisementSupported() && 115 !mBluetoothAdapter.isPeripheralModeSupported()) { 116 postStartFailure(callback, 117 AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED); 118 return; 119 } 120 if (totalBytes(advertiseData) > MAX_ADVERTISING_DATA_BYTES || 121 totalBytes(scanResponse) > MAX_ADVERTISING_DATA_BYTES) { 122 postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE); 123 return; 124 } 125 if (mLeAdvertisers.containsKey(callback)) { 126 postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED); 127 return; 128 } 129 130 IBluetoothGatt gatt; 131 try { 132 gatt = mBluetoothManager.getBluetoothGatt(); 133 } catch (RemoteException e) { 134 Log.e(TAG, "Failed to get Bluetooth gatt - ", e); 135 postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); 136 return; 137 } 138 AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData, 139 scanResponse, settings, gatt); 140 wrapper.startRegisteration(); 141 } 142 } 143 144 /** 145 * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in 146 * {@link BluetoothLeAdvertiser#startAdvertising}. 147 * <p> 148 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 149 * 150 * @param callback {@link AdvertiseCallback} identifies the advertising instance to stop. 151 */ 152 public void stopAdvertising(final AdvertiseCallback callback) { 153 synchronized (mLeAdvertisers) { 154 BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); 155 if (callback == null) { 156 throw new IllegalArgumentException("callback cannot be null"); 157 } 158 AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(callback); 159 if (wrapper == null) return; 160 wrapper.stopAdvertising(); 161 } 162 } 163 164 /** 165 * Cleans up advertise clients. Should be called when bluetooth is down. 166 * 167 * @hide 168 */ 169 public void cleanup() { 170 mLeAdvertisers.clear(); 171 } 172 173 // Compute the size of the advertise data. 174 private int totalBytes(AdvertiseData data) { 175 if (data == null) return 0; 176 int size = FLAGS_FIELD_BYTES; // flags field is always set. 177 if (data.getServiceUuids() != null) { 178 int num16BitUuids = 0; 179 int num32BitUuids = 0; 180 int num128BitUuids = 0; 181 for (ParcelUuid uuid : data.getServiceUuids()) { 182 if (BluetoothUuid.is16BitUuid(uuid)) { 183 ++num16BitUuids; 184 } else if (BluetoothUuid.is32BitUuid(uuid)) { 185 ++num32BitUuids; 186 } else { 187 ++num128BitUuids; 188 } 189 } 190 // 16 bit service uuids are grouped into one field when doing advertising. 191 if (num16BitUuids != 0) { 192 size += OVERHEAD_BYTES_PER_FIELD + 193 num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT; 194 } 195 // 32 bit service uuids are grouped into one field when doing advertising. 196 if (num32BitUuids != 0) { 197 size += OVERHEAD_BYTES_PER_FIELD + 198 num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT; 199 } 200 // 128 bit service uuids are grouped into one field when doing advertising. 201 if (num128BitUuids != 0) { 202 size += OVERHEAD_BYTES_PER_FIELD + 203 num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT; 204 } 205 } 206 for (ParcelUuid uuid : data.getServiceData().keySet()) { 207 size += OVERHEAD_BYTES_PER_FIELD + SERVICE_DATA_UUID_LENGTH 208 + byteLength(data.getServiceData().get(uuid)); 209 } 210 for (int i = 0; i < data.getManufacturerSpecificData().size(); ++i) { 211 size += OVERHEAD_BYTES_PER_FIELD + MANUFACTURER_SPECIFIC_DATA_LENGTH + 212 byteLength(data.getManufacturerSpecificData().valueAt(i)); 213 } 214 if (data.getIncludeTxPowerLevel()) { 215 size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte. 216 } 217 if (data.getIncludeDeviceName() && mBluetoothAdapter.getName() != null) { 218 size += OVERHEAD_BYTES_PER_FIELD + mBluetoothAdapter.getName().length(); 219 } 220 return size; 221 } 222 223 private int byteLength(byte[] array) { 224 return array == null ? 0 : array.length; 225 } 226 227 /** 228 * Bluetooth GATT interface callbacks for advertising. 229 */ 230 private class AdvertiseCallbackWrapper extends BluetoothGattCallbackWrapper { 231 private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000; 232 private final AdvertiseCallback mAdvertiseCallback; 233 private final AdvertiseData mAdvertisement; 234 private final AdvertiseData mScanResponse; 235 private final AdvertiseSettings mSettings; 236 private final IBluetoothGatt mBluetoothGatt; 237 238 // mClientIf 0: not registered 239 // -1: scan stopped 240 // >0: registered and scan started 241 private int mClientIf; 242 private boolean mIsAdvertising = false; 243 244 public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback, 245 AdvertiseData advertiseData, AdvertiseData scanResponse, 246 AdvertiseSettings settings, 247 IBluetoothGatt bluetoothGatt) { 248 mAdvertiseCallback = advertiseCallback; 249 mAdvertisement = advertiseData; 250 mScanResponse = scanResponse; 251 mSettings = settings; 252 mBluetoothGatt = bluetoothGatt; 253 mClientIf = 0; 254 } 255 256 public void startRegisteration() { 257 synchronized (this) { 258 if (mClientIf == -1) return; 259 260 try { 261 UUID uuid = UUID.randomUUID(); 262 mBluetoothGatt.registerClient(new ParcelUuid(uuid), this); 263 wait(LE_CALLBACK_TIMEOUT_MILLIS); 264 } catch (InterruptedException | RemoteException e) { 265 Log.e(TAG, "Failed to start registeration", e); 266 } 267 if (mClientIf > 0 && mIsAdvertising) { 268 mLeAdvertisers.put(mAdvertiseCallback, this); 269 } else if (mClientIf <= 0) { 270 // Post internal error if registration failed. 271 postStartFailure(mAdvertiseCallback, 272 AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); 273 } else { 274 // Unregister application if it's already registered but advertise failed. 275 try { 276 mBluetoothGatt.unregisterClient(mClientIf); 277 mClientIf = -1; 278 } catch (RemoteException e) { 279 Log.e(TAG, "remote exception when unregistering", e); 280 } 281 } 282 } 283 } 284 285 public void stopAdvertising() { 286 synchronized (this) { 287 try { 288 mBluetoothGatt.stopMultiAdvertising(mClientIf); 289 wait(LE_CALLBACK_TIMEOUT_MILLIS); 290 } catch (InterruptedException | RemoteException e) { 291 Log.e(TAG, "Failed to stop advertising", e); 292 } 293 // Advertise callback should have been removed from LeAdvertisers when 294 // onMultiAdvertiseCallback was called. In case onMultiAdvertiseCallback is never 295 // invoked and wait timeout expires, remove callback here. 296 if (mLeAdvertisers.containsKey(mAdvertiseCallback)) { 297 mLeAdvertisers.remove(mAdvertiseCallback); 298 } 299 } 300 } 301 302 /** 303 * Application interface registered - app is ready to go 304 */ 305 @Override 306 public void onClientRegistered(int status, int clientIf) { 307 Log.d(TAG, "onClientRegistered() - status=" + status + " clientIf=" + clientIf); 308 synchronized (this) { 309 if (status == BluetoothGatt.GATT_SUCCESS) { 310 mClientIf = clientIf; 311 try { 312 mBluetoothGatt.startMultiAdvertising(mClientIf, mAdvertisement, 313 mScanResponse, mSettings); 314 return; 315 } catch (RemoteException e) { 316 Log.e(TAG, "failed to start advertising", e); 317 } 318 } 319 // Registration failed. 320 mClientIf = -1; 321 notifyAll(); 322 } 323 } 324 325 @Override 326 public void onMultiAdvertiseCallback(int status, boolean isStart, 327 AdvertiseSettings settings) { 328 synchronized (this) { 329 if (isStart) { 330 if (status == AdvertiseCallback.ADVERTISE_SUCCESS) { 331 // Start success 332 mIsAdvertising = true; 333 postStartSuccess(mAdvertiseCallback, settings); 334 } else { 335 // Start failure. 336 postStartFailure(mAdvertiseCallback, status); 337 } 338 } else { 339 // unregister client for stop. 340 try { 341 mBluetoothGatt.unregisterClient(mClientIf); 342 mClientIf = -1; 343 mIsAdvertising = false; 344 mLeAdvertisers.remove(mAdvertiseCallback); 345 } catch (RemoteException e) { 346 Log.e(TAG, "remote exception when unregistering", e); 347 } 348 } 349 notifyAll(); 350 } 351 352 } 353 } 354 355 private void postStartFailure(final AdvertiseCallback callback, final int error) { 356 mHandler.post(new Runnable() { 357 @Override 358 public void run() { 359 callback.onStartFailure(error); 360 } 361 }); 362 } 363 364 private void postStartSuccess(final AdvertiseCallback callback, 365 final AdvertiseSettings settings) { 366 mHandler.post(new Runnable() { 367 368 @Override 369 public void run() { 370 callback.onStartSuccess(settings); 371 } 372 }); 373 } 374} 375