AdvertiseManager.java revision 96509d5ff7109bc42a4c25143f70ec41a127d109
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.
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 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     */
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     */
81    void start() {
82        HandlerThread thread = new HandlerThread("BluetoothAdvertiseManager");
83        thread.start();
84        mHandler = new ClientHandler(thread.getLooper());
85    }
86
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     */
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     */
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     */
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.
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
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
160        ClientHandler(Looper looper) {
161            super(looper);
162        }
163
164        @Override
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
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.
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.
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
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
283        void stopAdvertising(AdvertiseClient client) {
284            gattClientDisableAdvNative(client.clientIf);
285        }
286
287        private void resetCountDownLatch() {
288            mLatch = new CountDownLatch(1);
289        }
290
291        // Returns true if mLatch reaches 0, false if timeout or interrupted.
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
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
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.
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.
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.
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.
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).
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
431        private native void gattClientDisableAdvNative(int client_if);
432
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
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
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
446    private void logd(String s) {
447        if (DBG) {
448            Log.d(TAG, s);
449        }
450    }
451
452    private void loge(String s, Exception e) {
453        Log.e(TAG, s, e);
454    }
455
456}
457