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