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