Home | History | Annotate | Download | only in gatt
      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. TODO: add tests.
     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 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