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      */
AdvertiseManager(GattService service, AdapterService adapterService)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      */
start()83     void start() {
84         HandlerThread thread = new HandlerThread("BluetoothAdvertiseManager");
85         thread.start();
86         mHandler = new ClientHandler(thread.getLooper());
87     }
88 
cleanup()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      */
startAdvertising(AdvertiseClient client)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      */
stopAdvertising(AdvertiseClient client)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      */
callbackDone(int clientIf, int status)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.
postCallback(int clientIf, int status)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 
getAdvertiseClient(int clientIf)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 
ClientHandler(Looper looper)162         ClientHandler(Looper looper) {
163             super(looper);
164         }
165 
166         @Override
handleMessage(Message msg)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 
handleStartAdvertising(AdvertiseClient client)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.
handleStopAdvertising(AdvertiseClient client)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.
maxAdvertiseInstances()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.
startAdverising(AdvertiseClient client)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 
startMultiAdvertising(AdvertiseClient client)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 
startSingleAdvertising(AdvertiseClient client)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 
stopAdvertising(AdvertiseClient client)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 
resetCountDownLatch()326         private void resetCountDownLatch() {
327             mLatch = new CountDownLatch(1);
328         }
329 
330         // Returns true if mLatch reaches 0, false if timeout or interrupted.
waitForCallback()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 
enableAdvertising(AdvertiseClient client)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 
setAdvertisingData(AdvertiseClient client, AdvertiseData data, boolean isScanResponse)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.
getManufacturerData(AdvertiseData advertiseData)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.
getServiceData(AdvertiseData advertiseData)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.
getTxPowerLevel(AdvertiseSettings settings)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.
getAdvertisingEventType(AdvertiseClient client)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).
getAdvertisingIntervalUnit(AdvertiseSettings settings)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
gattClientDisableAdvNative(int client_if)481         private native void gattClientDisableAdvNative(int client_if);
482 
gattClientEnableAdvNative(int client_if, int min_interval, int max_interval, int adv_type, int chnl_map, int tx_power, int timeout_s)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 
gattClientUpdateAdvNative(int client_if, int min_interval, int max_interval, int adv_type, int chnl_map, int tx_power, int timeout_s)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 
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)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 
gattSetAdvDataNative(int serverIf, boolean setScanRsp, boolean inclName, boolean inclTxPower, int minSlaveConnectionInterval, int maxSlaveConnectionInterval, int appearance, byte[] manufacturerData, byte[] serviceData, byte[] serviceUuid)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 
gattAdvertiseNative(int client_if, boolean start)499         private native void gattAdvertiseNative(int client_if, boolean start);
500     }
501 
logd(String s)502     private void logd(String s) {
503         if (DBG) {
504             Log.d(TAG, s);
505         }
506     }
507 
loge(String s, Exception e)508     private void loge(String s, Exception e) {
509         Log.e(TAG, s, e);
510     }
511 
512 }
513