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 17 package android.bluetooth.le; 18 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothGatt; 21 import android.bluetooth.BluetoothGattCallbackWrapper; 22 import android.bluetooth.BluetoothUuid; 23 import android.bluetooth.IBluetoothGatt; 24 import android.bluetooth.IBluetoothManager; 25 import android.os.Handler; 26 import android.os.Looper; 27 import android.os.ParcelUuid; 28 import android.os.RemoteException; 29 import android.util.Log; 30 31 import java.util.HashMap; 32 import java.util.Map; 33 import 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 */ 48 public 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 boolean isConnectable = settings.isConnectable(); 121 if (totalBytes(advertiseData, isConnectable) > MAX_ADVERTISING_DATA_BYTES || 122 totalBytes(scanResponse, false) > MAX_ADVERTISING_DATA_BYTES) { 123 postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE); 124 return; 125 } 126 if (mLeAdvertisers.containsKey(callback)) { 127 postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED); 128 return; 129 } 130 131 IBluetoothGatt gatt; 132 try { 133 gatt = mBluetoothManager.getBluetoothGatt(); 134 } catch (RemoteException e) { 135 Log.e(TAG, "Failed to get Bluetooth gatt - ", e); 136 postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); 137 return; 138 } 139 AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData, 140 scanResponse, settings, gatt); 141 wrapper.startRegisteration(); 142 } 143 } 144 145 /** 146 * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in 147 * {@link BluetoothLeAdvertiser#startAdvertising}. 148 * <p> 149 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 150 * 151 * @param callback {@link AdvertiseCallback} identifies the advertising instance to stop. 152 */ 153 public void stopAdvertising(final AdvertiseCallback callback) { 154 synchronized (mLeAdvertisers) { 155 BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); 156 if (callback == null) { 157 throw new IllegalArgumentException("callback cannot be null"); 158 } 159 AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(callback); 160 if (wrapper == null) return; 161 wrapper.stopAdvertising(); 162 } 163 } 164 165 /** 166 * Cleans up advertise clients. Should be called when bluetooth is down. 167 * 168 * @hide 169 */ 170 public void cleanup() { 171 mLeAdvertisers.clear(); 172 } 173 174 // Compute the size of advertisement data or scan resp 175 private int totalBytes(AdvertiseData data, boolean isFlagsIncluded) { 176 if (data == null) return 0; 177 // Flags field is omitted if the advertising is not connectable. 178 int size = (isFlagsIncluded) ? FLAGS_FIELD_BYTES : 0; 179 if (data.getServiceUuids() != null) { 180 int num16BitUuids = 0; 181 int num32BitUuids = 0; 182 int num128BitUuids = 0; 183 for (ParcelUuid uuid : data.getServiceUuids()) { 184 if (BluetoothUuid.is16BitUuid(uuid)) { 185 ++num16BitUuids; 186 } else if (BluetoothUuid.is32BitUuid(uuid)) { 187 ++num32BitUuids; 188 } else { 189 ++num128BitUuids; 190 } 191 } 192 // 16 bit service uuids are grouped into one field when doing advertising. 193 if (num16BitUuids != 0) { 194 size += OVERHEAD_BYTES_PER_FIELD + 195 num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT; 196 } 197 // 32 bit service uuids are grouped into one field when doing advertising. 198 if (num32BitUuids != 0) { 199 size += OVERHEAD_BYTES_PER_FIELD + 200 num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT; 201 } 202 // 128 bit service uuids are grouped into one field when doing advertising. 203 if (num128BitUuids != 0) { 204 size += OVERHEAD_BYTES_PER_FIELD + 205 num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT; 206 } 207 } 208 for (ParcelUuid uuid : data.getServiceData().keySet()) { 209 size += OVERHEAD_BYTES_PER_FIELD + SERVICE_DATA_UUID_LENGTH 210 + byteLength(data.getServiceData().get(uuid)); 211 } 212 for (int i = 0; i < data.getManufacturerSpecificData().size(); ++i) { 213 size += OVERHEAD_BYTES_PER_FIELD + MANUFACTURER_SPECIFIC_DATA_LENGTH + 214 byteLength(data.getManufacturerSpecificData().valueAt(i)); 215 } 216 if (data.getIncludeTxPowerLevel()) { 217 size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte. 218 } 219 if (data.getIncludeDeviceName() && mBluetoothAdapter.getName() != null) { 220 size += OVERHEAD_BYTES_PER_FIELD + mBluetoothAdapter.getName().length(); 221 } 222 return size; 223 } 224 225 private int byteLength(byte[] array) { 226 return array == null ? 0 : array.length; 227 } 228 229 /** 230 * Bluetooth GATT interface callbacks for advertising. 231 */ 232 private class AdvertiseCallbackWrapper extends BluetoothGattCallbackWrapper { 233 private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000; 234 private final AdvertiseCallback mAdvertiseCallback; 235 private final AdvertiseData mAdvertisement; 236 private final AdvertiseData mScanResponse; 237 private final AdvertiseSettings mSettings; 238 private final IBluetoothGatt mBluetoothGatt; 239 240 // mClientIf 0: not registered 241 // -1: scan stopped 242 // >0: registered and scan started 243 private int mClientIf; 244 private boolean mIsAdvertising = false; 245 246 public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback, 247 AdvertiseData advertiseData, AdvertiseData scanResponse, 248 AdvertiseSettings settings, 249 IBluetoothGatt bluetoothGatt) { 250 mAdvertiseCallback = advertiseCallback; 251 mAdvertisement = advertiseData; 252 mScanResponse = scanResponse; 253 mSettings = settings; 254 mBluetoothGatt = bluetoothGatt; 255 mClientIf = 0; 256 } 257 258 public void startRegisteration() { 259 synchronized (this) { 260 if (mClientIf == -1) return; 261 262 try { 263 UUID uuid = UUID.randomUUID(); 264 mBluetoothGatt.registerClient(new ParcelUuid(uuid), this); 265 wait(LE_CALLBACK_TIMEOUT_MILLIS); 266 } catch (InterruptedException | RemoteException e) { 267 Log.e(TAG, "Failed to start registeration", e); 268 } 269 if (mClientIf > 0 && mIsAdvertising) { 270 mLeAdvertisers.put(mAdvertiseCallback, this); 271 } else if (mClientIf <= 0) { 272 // Post internal error if registration failed. 273 postStartFailure(mAdvertiseCallback, 274 AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); 275 } else { 276 // Unregister application if it's already registered but advertise failed. 277 try { 278 mBluetoothGatt.unregisterClient(mClientIf); 279 mClientIf = -1; 280 } catch (RemoteException e) { 281 Log.e(TAG, "remote exception when unregistering", e); 282 } 283 } 284 } 285 } 286 287 public void stopAdvertising() { 288 synchronized (this) { 289 try { 290 mBluetoothGatt.stopMultiAdvertising(mClientIf); 291 wait(LE_CALLBACK_TIMEOUT_MILLIS); 292 } catch (InterruptedException | RemoteException e) { 293 Log.e(TAG, "Failed to stop advertising", e); 294 } 295 // Advertise callback should have been removed from LeAdvertisers when 296 // onMultiAdvertiseCallback was called. In case onMultiAdvertiseCallback is never 297 // invoked and wait timeout expires, remove callback here. 298 if (mLeAdvertisers.containsKey(mAdvertiseCallback)) { 299 mLeAdvertisers.remove(mAdvertiseCallback); 300 } 301 } 302 } 303 304 /** 305 * Application interface registered - app is ready to go 306 */ 307 @Override 308 public void onClientRegistered(int status, int clientIf) { 309 Log.d(TAG, "onClientRegistered() - status=" + status + " clientIf=" + clientIf); 310 synchronized (this) { 311 if (status == BluetoothGatt.GATT_SUCCESS) { 312 mClientIf = clientIf; 313 try { 314 mBluetoothGatt.startMultiAdvertising(mClientIf, mAdvertisement, 315 mScanResponse, mSettings); 316 return; 317 } catch (RemoteException e) { 318 Log.e(TAG, "failed to start advertising", e); 319 } 320 } 321 // Registration failed. 322 mClientIf = -1; 323 notifyAll(); 324 } 325 } 326 327 @Override 328 public void onMultiAdvertiseCallback(int status, boolean isStart, 329 AdvertiseSettings settings) { 330 synchronized (this) { 331 if (isStart) { 332 if (status == AdvertiseCallback.ADVERTISE_SUCCESS) { 333 // Start success 334 mIsAdvertising = true; 335 postStartSuccess(mAdvertiseCallback, settings); 336 } else { 337 // Start failure. 338 postStartFailure(mAdvertiseCallback, status); 339 } 340 } else { 341 // unregister client for stop. 342 try { 343 mBluetoothGatt.unregisterClient(mClientIf); 344 mClientIf = -1; 345 mIsAdvertising = false; 346 mLeAdvertisers.remove(mAdvertiseCallback); 347 } catch (RemoteException e) { 348 Log.e(TAG, "remote exception when unregistering", e); 349 } 350 } 351 notifyAll(); 352 } 353 354 } 355 } 356 357 private void postStartFailure(final AdvertiseCallback callback, final int error) { 358 mHandler.post(new Runnable() { 359 @Override 360 public void run() { 361 callback.onStartFailure(error); 362 } 363 }); 364 } 365 366 private void postStartSuccess(final AdvertiseCallback callback, 367 final AdvertiseSettings settings) { 368 mHandler.post(new Runnable() { 369 370 @Override 371 public void run() { 372 callback.onStartSuccess(settings); 373 } 374 }); 375 } 376 } 377