• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
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 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      */
AdvertiseManager(GattService service)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      */
start()81     void start() {
82         HandlerThread thread = new HandlerThread("BluetoothAdvertiseManager");
83         thread.start();
84         mHandler = new ClientHandler(thread.getLooper());
85     }
86 
cleanup()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      */
startAdvertising(AdvertiseClient client)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      */
stopAdvertising(AdvertiseClient client)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      */
callbackDone(int clientIf, int status)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.
postCallback(int clientIf, int status)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 
getAdvertiseClient(int clientIf)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 
ClientHandler(Looper looper)160         ClientHandler(Looper looper) {
161             super(looper);
162         }
163 
164         @Override
handleMessage(Message msg)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 
handleStartAdvertising(AdvertiseClient client)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.
handleStopAdvertising(AdvertiseClient client)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.
maxAdvertiseInstances()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 
startAdverising(AdvertiseClient client)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 
stopAdvertising(AdvertiseClient client)283         void stopAdvertising(AdvertiseClient client) {
284             gattClientDisableAdvNative(client.clientIf);
285         }
286 
resetCountDownLatch()287         private void resetCountDownLatch() {
288             mLatch = new CountDownLatch(1);
289         }
290 
291         // Returns true if mLatch reaches 0, false if timeout or interrupted.
waitForCallback()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 
enableAdvertising(AdvertiseClient client)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 
setAdvertisingData(int clientIf, AdvertiseData data, boolean isScanResponse)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.
getManufacturerData(AdvertiseData advertiseData)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.
getServiceData(AdvertiseData advertiseData)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.
getTxPowerLevel(AdvertiseSettings settings)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.
getAdvertisingEventType(AdvertiseClient client)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).
getAdvertisingIntervalUnit(AdvertiseSettings settings)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
gattClientDisableAdvNative(int client_if)431         private native void gattClientDisableAdvNative(int client_if);
432 
gattClientEnableAdvNative(int client_if, int min_interval, int max_interval, int adv_type, int chnl_map, int tx_power, int timeout_s)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 
gattClientUpdateAdvNative(int client_if, int min_interval, int max_interval, int adv_type, int chnl_map, int tx_power, int timeout_s)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 
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)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 
logd(String s)446     private void logd(String s) {
447         if (DBG) {
448             Log.d(TAG, s);
449         }
450     }
451 
loge(String s, Exception e)452     private void loge(String s, Exception e) {
453         Log.e(TAG, s, e);
454     }
455 
456 }
457