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 com.android.bluetooth.gatt; 18 19import android.bluetooth.BluetoothUuid; 20import android.bluetooth.le.AdvertiseCallback; 21import android.bluetooth.le.AdvertiseData; 22import android.bluetooth.le.AdvertiseSettings; 23import android.os.Handler; 24import android.os.HandlerThread; 25import android.os.Looper; 26import android.os.Message; 27import android.os.ParcelUuid; 28import android.os.RemoteException; 29import android.util.Log; 30 31import com.android.bluetooth.Utils; 32import com.android.bluetooth.btservice.AdapterService; 33 34import java.nio.ByteBuffer; 35import java.nio.ByteOrder; 36import java.util.HashSet; 37import java.util.Set; 38import java.util.UUID; 39import java.util.concurrent.CountDownLatch; 40import java.util.concurrent.TimeUnit; 41 42/** 43 * Manages Bluetooth LE advertising operations and interacts with bluedroid stack. TODO: add tests. 44 * 45 * @hide 46 */ 47class AdvertiseManager { 48 private static final boolean DBG = GattServiceConfig.DBG; 49 private static final String TAG = GattServiceConfig.TAG_PREFIX + "AdvertiseManager"; 50 51 // Timeout for each controller operation. 52 private static final int OPERATION_TIME_OUT_MILLIS = 500; 53 54 // Message for advertising operations. 55 private static final int MSG_START_ADVERTISING = 0; 56 private static final int MSG_STOP_ADVERTISING = 1; 57 58 private final GattService mService; 59 private final AdapterService mAdapterService; 60 private final Set<AdvertiseClient> mAdvertiseClients; 61 private final AdvertiseNative mAdvertiseNative; 62 63 // Handles advertise operations. 64 private ClientHandler mHandler; 65 66 // CountDownLatch for blocking advertise operations. 67 private CountDownLatch mLatch; 68 69 /** 70 * Constructor of {@link AdvertiseManager}. 71 */ 72 AdvertiseManager(GattService service, AdapterService adapterService) { 73 logd("advertise manager created"); 74 mService = service; 75 mAdapterService = adapterService; 76 mAdvertiseClients = new HashSet<AdvertiseClient>(); 77 mAdvertiseNative = new AdvertiseNative(); 78 } 79 80 /** 81 * Start a {@link HandlerThread} that handles advertising operations. 82 */ 83 void start() { 84 HandlerThread thread = new HandlerThread("BluetoothAdvertiseManager"); 85 thread.start(); 86 mHandler = new ClientHandler(thread.getLooper()); 87 } 88 89 void cleanup() { 90 logd("advertise clients cleared"); 91 mAdvertiseClients.clear(); 92 } 93 94 /** 95 * Start BLE advertising. 96 * 97 * @param client Advertise client. 98 */ 99 void startAdvertising(AdvertiseClient client) { 100 if (client == null) { 101 return; 102 } 103 Message message = new Message(); 104 message.what = MSG_START_ADVERTISING; 105 message.obj = client; 106 mHandler.sendMessage(message); 107 } 108 109 /** 110 * Stop BLE advertising. 111 */ 112 void stopAdvertising(AdvertiseClient client) { 113 if (client == null) { 114 return; 115 } 116 Message message = new Message(); 117 message.what = MSG_STOP_ADVERTISING; 118 message.obj = client; 119 mHandler.sendMessage(message); 120 } 121 122 /** 123 * Signals the callback is received. 124 * 125 * @param clientIf Identifier for the client. 126 * @param status Status of the callback. 127 */ 128 void callbackDone(int clientIf, int status) { 129 if (status == AdvertiseCallback.ADVERTISE_SUCCESS) { 130 mLatch.countDown(); 131 } else { 132 // Note in failure case we'll wait for the latch to timeout(which takes 100ms) and 133 // the mClientHandler thread will be blocked till timeout. 134 postCallback(clientIf, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); 135 } 136 } 137 138 // Post callback status to app process. 139 private void postCallback(int clientIf, int status) { 140 try { 141 AdvertiseClient client = getAdvertiseClient(clientIf); 142 AdvertiseSettings settings = (client == null) ? null : client.settings; 143 boolean isStart = true; 144 mService.onMultipleAdvertiseCallback(clientIf, status, isStart, settings); 145 } catch (RemoteException e) { 146 loge("failed onMultipleAdvertiseCallback", e); 147 } 148 } 149 150 private AdvertiseClient getAdvertiseClient(int clientIf) { 151 for (AdvertiseClient client : mAdvertiseClients) { 152 if (client.clientIf == clientIf) { 153 return client; 154 } 155 } 156 return null; 157 } 158 159 // Handler class that handles BLE advertising operations. 160 private class ClientHandler extends Handler { 161 162 ClientHandler(Looper looper) { 163 super(looper); 164 } 165 166 @Override 167 public void handleMessage(Message msg) { 168 logd("message : " + msg.what); 169 AdvertiseClient client = (AdvertiseClient) msg.obj; 170 switch (msg.what) { 171 case MSG_START_ADVERTISING: 172 handleStartAdvertising(client); 173 break; 174 case MSG_STOP_ADVERTISING: 175 handleStopAdvertising(client); 176 break; 177 default: 178 // Shouldn't happen. 179 Log.e(TAG, "recieve an unknown message : " + msg.what); 180 break; 181 } 182 } 183 184 private void handleStartAdvertising(AdvertiseClient client) { 185 Utils.enforceAdminPermission(mService); 186 int clientIf = client.clientIf; 187 if (mAdvertiseClients.contains(clientIf)) { 188 postCallback(clientIf, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED); 189 return; 190 } 191 192 if (mAdvertiseClients.size() >= maxAdvertiseInstances()) { 193 postCallback(clientIf, 194 AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS); 195 return; 196 } 197 if (!mAdvertiseNative.startAdverising(client)) { 198 postCallback(clientIf, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); 199 return; 200 } 201 mAdvertiseClients.add(client); 202 postCallback(clientIf, AdvertiseCallback.ADVERTISE_SUCCESS); 203 } 204 205 // Handles stop advertising. 206 private void handleStopAdvertising(AdvertiseClient client) { 207 Utils.enforceAdminPermission(mService); 208 if (client == null) { 209 return; 210 } 211 logd("stop advertise for client " + client.clientIf); 212 mAdvertiseNative.stopAdvertising(client); 213 if (client.appDied) { 214 logd("app died - unregistering client : " + client.clientIf); 215 mService.unregisterClient(client.clientIf); 216 } 217 if (mAdvertiseClients.contains(client)) { 218 mAdvertiseClients.remove(client); 219 } 220 } 221 222 // Returns maximum advertise instances supported by controller. 223 int maxAdvertiseInstances() { 224 // Note numOfAdvtInstances includes the standard advertising instance. 225 // TODO: remove - 1 once the stack is able to include standard instance for multiple 226 // advertising. 227 if (mAdapterService.isMultiAdvertisementSupported()) { 228 return mAdapterService.getNumOfAdvertisementInstancesSupported() - 1; 229 } 230 if (mAdapterService.isPeripheralModeSupported()) { 231 return 1; 232 } 233 return 0; 234 } 235 } 236 237 // Class that wraps advertise native related constants, methods etc. 238 private class AdvertiseNative { 239 // Advertise interval for different modes. 240 private static final int ADVERTISING_INTERVAL_HIGH_MILLS = 1000; 241 private static final int ADVERTISING_INTERVAL_MEDIUM_MILLS = 250; 242 private static final int ADVERTISING_INTERVAL_LOW_MILLS = 100; 243 244 // Add some randomness to the advertising min/max interval so the controller can do some 245 // optimization. 246 private static final int ADVERTISING_INTERVAL_DELTA_UNIT = 10; 247 248 // The following constants should be kept the same as those defined in bt stack. 249 private static final int ADVERTISING_CHANNEL_37 = 1 << 0; 250 private static final int ADVERTISING_CHANNEL_38 = 1 << 1; 251 private static final int ADVERTISING_CHANNEL_39 = 1 << 2; 252 private static final int ADVERTISING_CHANNEL_ALL = 253 ADVERTISING_CHANNEL_37 | ADVERTISING_CHANNEL_38 | ADVERTISING_CHANNEL_39; 254 255 private static final int ADVERTISING_TX_POWER_MIN = 0; 256 private static final int ADVERTISING_TX_POWER_LOW = 1; 257 private static final int ADVERTISING_TX_POWER_MID = 2; 258 private static final int ADVERTISING_TX_POWER_UPPER = 3; 259 // Note this is not exposed to the Java API. 260 private static final int ADVERTISING_TX_POWER_MAX = 4; 261 262 // Note we don't expose connectable directed advertising to API. 263 private static final int ADVERTISING_EVENT_TYPE_CONNECTABLE = 0; 264 private static final int ADVERTISING_EVENT_TYPE_SCANNABLE = 2; 265 private static final int ADVERTISING_EVENT_TYPE_NON_CONNECTABLE = 3; 266 267 // TODO: Extract advertising logic into interface as we have multiple implementations now. 268 boolean startAdverising(AdvertiseClient client) { 269 if (!mAdapterService.isMultiAdvertisementSupported() && 270 !mAdapterService.isPeripheralModeSupported()) { 271 return false; 272 } 273 if (mAdapterService.isMultiAdvertisementSupported()) { 274 return startMultiAdvertising(client); 275 } 276 return startSingleAdvertising(client); 277 } 278 279 boolean startMultiAdvertising(AdvertiseClient client) { 280 logd("starting multi advertising"); 281 resetCountDownLatch(); 282 enableAdvertising(client); 283 if (!waitForCallback()) { 284 return false; 285 } 286 resetCountDownLatch(); 287 setAdvertisingData(client, client.advertiseData, false); 288 if (!waitForCallback()) { 289 return false; 290 } 291 if (client.scanResponse != null) { 292 resetCountDownLatch(); 293 setAdvertisingData(client, client.scanResponse, true); 294 if (!waitForCallback()) { 295 return false; 296 } 297 } 298 return true; 299 } 300 301 boolean startSingleAdvertising(AdvertiseClient client) { 302 logd("starting single advertising"); 303 resetCountDownLatch(); 304 enableAdvertising(client); 305 if (!waitForCallback()) { 306 return false; 307 } 308 setAdvertisingData(client, client.advertiseData, false); 309 return true; 310 } 311 312 void stopAdvertising(AdvertiseClient client) { 313 if (mAdapterService.isMultiAdvertisementSupported()) { 314 gattClientDisableAdvNative(client.clientIf); 315 } else { 316 gattAdvertiseNative(client.clientIf, false); 317 try { 318 mService.onAdvertiseInstanceDisabled( 319 AdvertiseCallback.ADVERTISE_SUCCESS, client.clientIf); 320 } catch (RemoteException e) { 321 Log.d(TAG, "failed onAdvertiseInstanceDisabled", e); 322 } 323 } 324 } 325 326 private void resetCountDownLatch() { 327 mLatch = new CountDownLatch(1); 328 } 329 330 // Returns true if mLatch reaches 0, false if timeout or interrupted. 331 private boolean waitForCallback() { 332 try { 333 return mLatch.await(OPERATION_TIME_OUT_MILLIS, TimeUnit.MILLISECONDS); 334 } catch (InterruptedException e) { 335 return false; 336 } 337 } 338 339 private void enableAdvertising(AdvertiseClient client) { 340 int clientIf = client.clientIf; 341 int minAdvertiseUnit = (int) getAdvertisingIntervalUnit(client.settings); 342 int maxAdvertiseUnit = minAdvertiseUnit + ADVERTISING_INTERVAL_DELTA_UNIT; 343 int advertiseEventType = getAdvertisingEventType(client); 344 int txPowerLevel = getTxPowerLevel(client.settings); 345 int advertiseTimeoutSeconds = (int) TimeUnit.MILLISECONDS.toSeconds( 346 client.settings.getTimeout()); 347 if (mAdapterService.isMultiAdvertisementSupported()) { 348 gattClientEnableAdvNative( 349 clientIf, 350 minAdvertiseUnit, maxAdvertiseUnit, 351 advertiseEventType, 352 ADVERTISING_CHANNEL_ALL, 353 txPowerLevel, 354 advertiseTimeoutSeconds); 355 } else { 356 gattAdvertiseNative(client.clientIf, true); 357 } 358 } 359 360 private void setAdvertisingData(AdvertiseClient client, AdvertiseData data, 361 boolean isScanResponse) { 362 if (data == null) { 363 return; 364 } 365 boolean includeName = data.getIncludeDeviceName(); 366 boolean includeTxPower = data.getIncludeTxPowerLevel(); 367 int appearance = 0; 368 byte[] manufacturerData = getManufacturerData(data); 369 370 byte[] serviceData = getServiceData(data); 371 byte[] serviceUuids; 372 if (data.getServiceUuids() == null) { 373 serviceUuids = new byte[0]; 374 } else { 375 ByteBuffer advertisingUuidBytes = ByteBuffer.allocate( 376 data.getServiceUuids().size() * 16) 377 .order(ByteOrder.LITTLE_ENDIAN); 378 for (ParcelUuid parcelUuid : data.getServiceUuids()) { 379 UUID uuid = parcelUuid.getUuid(); 380 // Least significant bits first as the advertising UUID should be in 381 // little-endian. 382 advertisingUuidBytes.putLong(uuid.getLeastSignificantBits()) 383 .putLong(uuid.getMostSignificantBits()); 384 } 385 serviceUuids = advertisingUuidBytes.array(); 386 } 387 if (mAdapterService.isMultiAdvertisementSupported()) { 388 gattClientSetAdvDataNative(client.clientIf, isScanResponse, includeName, 389 includeTxPower, appearance, 390 manufacturerData, serviceData, serviceUuids); 391 } else { 392 gattSetAdvDataNative(client.clientIf, isScanResponse, includeName, 393 includeTxPower, 0, 0, appearance, 394 manufacturerData, serviceData, serviceUuids); 395 } 396 } 397 398 // Combine manufacturer id and manufacturer data. 399 private byte[] getManufacturerData(AdvertiseData advertiseData) { 400 if (advertiseData.getManufacturerSpecificData().size() == 0) { 401 return new byte[0]; 402 } 403 int manufacturerId = advertiseData.getManufacturerSpecificData().keyAt(0); 404 byte[] manufacturerData = advertiseData.getManufacturerSpecificData().get( 405 manufacturerId); 406 int dataLen = 2 + (manufacturerData == null ? 0 : manufacturerData.length); 407 byte[] concated = new byte[dataLen]; 408 // / First two bytes are manufacturer id in little-endian. 409 concated[0] = (byte) (manufacturerId & 0xFF); 410 concated[1] = (byte) ((manufacturerId >> 8) & 0xFF); 411 if (manufacturerData != null) { 412 System.arraycopy(manufacturerData, 0, concated, 2, manufacturerData.length); 413 } 414 return concated; 415 } 416 417 // Combine service UUID and service data. 418 private byte[] getServiceData(AdvertiseData advertiseData) { 419 if (advertiseData.getServiceData().isEmpty()) { 420 return new byte[0]; 421 } 422 ParcelUuid uuid = advertiseData.getServiceData().keySet().iterator().next(); 423 byte[] serviceData = advertiseData.getServiceData().get(uuid); 424 int dataLen = 2 + (serviceData == null ? 0 : serviceData.length); 425 byte[] concated = new byte[dataLen]; 426 // Extract 16 bit UUID value. 427 int uuidValue = BluetoothUuid.getServiceIdentifierFromParcelUuid( 428 uuid); 429 // First two bytes are service data UUID in little-endian. 430 concated[0] = (byte) (uuidValue & 0xFF); 431 concated[1] = (byte) ((uuidValue >> 8) & 0xFF); 432 if (serviceData != null) { 433 System.arraycopy(serviceData, 0, concated, 2, serviceData.length); 434 } 435 return concated; 436 } 437 438 // Convert settings tx power level to stack tx power level. 439 private int getTxPowerLevel(AdvertiseSettings settings) { 440 switch (settings.getTxPowerLevel()) { 441 case AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW: 442 return ADVERTISING_TX_POWER_MIN; 443 case AdvertiseSettings.ADVERTISE_TX_POWER_LOW: 444 return ADVERTISING_TX_POWER_LOW; 445 case AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM: 446 return ADVERTISING_TX_POWER_MID; 447 case AdvertiseSettings.ADVERTISE_TX_POWER_HIGH: 448 return ADVERTISING_TX_POWER_UPPER; 449 default: 450 // Shouldn't happen, just in case. 451 return ADVERTISING_TX_POWER_MID; 452 } 453 } 454 455 // Convert advertising event type to stack values. 456 private int getAdvertisingEventType(AdvertiseClient client) { 457 AdvertiseSettings settings = client.settings; 458 if (settings.isConnectable()) { 459 return ADVERTISING_EVENT_TYPE_CONNECTABLE; 460 } 461 return client.scanResponse == null ? ADVERTISING_EVENT_TYPE_NON_CONNECTABLE 462 : ADVERTISING_EVENT_TYPE_SCANNABLE; 463 } 464 465 // Convert advertising milliseconds to advertising units(one unit is 0.625 millisecond). 466 private long getAdvertisingIntervalUnit(AdvertiseSettings settings) { 467 switch (settings.getMode()) { 468 case AdvertiseSettings.ADVERTISE_MODE_LOW_POWER: 469 return Utils.millsToUnit(ADVERTISING_INTERVAL_HIGH_MILLS); 470 case AdvertiseSettings.ADVERTISE_MODE_BALANCED: 471 return Utils.millsToUnit(ADVERTISING_INTERVAL_MEDIUM_MILLS); 472 case AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY: 473 return Utils.millsToUnit(ADVERTISING_INTERVAL_LOW_MILLS); 474 default: 475 // Shouldn't happen, just in case. 476 return Utils.millsToUnit(ADVERTISING_INTERVAL_HIGH_MILLS); 477 } 478 } 479 480 // Native functions 481 private native void gattClientDisableAdvNative(int client_if); 482 483 private native void gattClientEnableAdvNative(int client_if, 484 int min_interval, int max_interval, int adv_type, int chnl_map, 485 int tx_power, int timeout_s); 486 487 private native void gattClientUpdateAdvNative(int client_if, 488 int min_interval, int max_interval, int adv_type, int chnl_map, 489 int tx_power, int timeout_s); 490 491 private native void gattClientSetAdvDataNative(int client_if, 492 boolean set_scan_rsp, boolean incl_name, boolean incl_txpower, int appearance, 493 byte[] manufacturer_data, byte[] service_data, byte[] service_uuid); 494 495 private native void gattSetAdvDataNative(int serverIf, boolean setScanRsp, boolean inclName, 496 boolean inclTxPower, int minSlaveConnectionInterval, int maxSlaveConnectionInterval, 497 int appearance, byte[] manufacturerData, byte[] serviceData, byte[] serviceUuid); 498 499 private native void gattAdvertiseNative(int client_if, boolean start); 500 } 501 502 private void logd(String s) { 503 if (DBG) { 504 Log.d(TAG, s); 505 } 506 } 507 508 private void loge(String s, Exception e) { 509 Log.e(TAG, s, e); 510 } 511 512} 513