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