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. TODO: add tests.
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 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(client)) {
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