ScanManager.java revision 86c292ab0f0fed25345a2eaef0fd92ff9c72a9e5
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.BluetoothAdapter;
20import android.bluetooth.le.ScanFilter;
21import android.bluetooth.le.ScanSettings;
22import android.os.Handler;
23import android.os.HandlerThread;
24import android.os.Looper;
25import android.os.Message;
26import android.util.Log;
27
28import com.android.bluetooth.Utils;
29import com.android.bluetooth.btservice.AdapterService;
30
31import java.util.ArrayDeque;
32import java.util.ArrayList;
33import java.util.Deque;
34import java.util.HashMap;
35import java.util.HashSet;
36import java.util.List;
37import java.util.Map;
38import java.util.Set;
39import java.util.concurrent.CountDownLatch;
40import java.util.concurrent.TimeUnit;
41
42/**
43 * Class that handles Bluetooth LE scan related operations.
44 *
45 * @hide
46 */
47public class ScanManager {
48    private static final boolean DBG = GattServiceConfig.DBG;
49    private static final String TAG = GattServiceConfig.TAG_PREFIX + "ScanManager";
50
51    // Result type defined in bt stack. Need to be accessed by GattService.
52    static final int SCAN_RESULT_TYPE_TRUNCATED = 1;
53    static final int SCAN_RESULT_TYPE_FULL = 2;
54
55    // Internal messages for handling BLE scan operations.
56    private static final int MSG_START_BLE_SCAN = 0;
57    private static final int MSG_STOP_BLE_SCAN = 1;
58    private static final int MSG_FLUSH_BATCH_RESULTS = 2;
59
60    // Timeout for each controller operation.
61    private static final int OPERATION_TIME_OUT_MILLIS = 500;
62
63    private GattService mService;
64    private ScanNative mScanNative;
65    private ClientHandler mHandler;
66
67    private Set<ScanClient> mRegularScanClients;
68    private Set<ScanClient> mBatchClients;
69
70    private CountDownLatch mLatch;
71
72    ScanManager(GattService service) {
73        mRegularScanClients = new HashSet<ScanClient>();
74        mBatchClients = new HashSet<ScanClient>();
75        mScanNative = new ScanNative();
76        mService = service;
77    }
78
79    void start() {
80        HandlerThread thread = new HandlerThread("BluetoothScanManager");
81        thread.start();
82        mHandler = new ClientHandler(thread.getLooper());
83    }
84
85    void cleanup() {
86        mRegularScanClients.clear();
87        mBatchClients.clear();
88    }
89
90    /**
91     * Returns the combined scan queue of regular scans and batch scans.
92     */
93    List<ScanClient> scanQueue() {
94        List<ScanClient> clients = new ArrayList<>();
95        clients.addAll(mRegularScanClients);
96        clients.addAll(mBatchClients);
97        return clients;
98    }
99
100    void startScan(ScanClient client) {
101        sendMessage(MSG_START_BLE_SCAN, client);
102    }
103
104    void stopScan(ScanClient client) {
105        sendMessage(MSG_STOP_BLE_SCAN, client);
106    }
107
108    void flushBatchScanResults(ScanClient client) {
109        sendMessage(MSG_FLUSH_BATCH_RESULTS, client);
110    }
111
112    void callbackDone(int clientIf, int status) {
113        logd("callback done for clientIf - " + clientIf + " status - " + status);
114        if (status == 0) {
115            mLatch.countDown();
116        }
117        // TODO: add a callback for scan failure.
118    }
119
120    private void sendMessage(int what, ScanClient client) {
121        Message message = new Message();
122        message.what = what;
123        message.obj = client;
124        mHandler.sendMessage(message);
125    }
126
127    private boolean isFilteringSupported() {
128        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
129        return adapter.isOffloadedFilteringSupported();
130    }
131
132    // Handler class that handles BLE scan operations.
133    private class ClientHandler extends Handler {
134
135        ClientHandler(Looper looper) {
136            super(looper);
137        }
138
139        @Override
140        public void handleMessage(Message msg) {
141            ScanClient client = (ScanClient) msg.obj;
142            switch (msg.what) {
143                case MSG_START_BLE_SCAN:
144                    handleStartScan(client);
145                    break;
146                case MSG_STOP_BLE_SCAN:
147                    handleStopScan(client);
148                    break;
149                case MSG_FLUSH_BATCH_RESULTS:
150                    handleFlushBatchResults(client);
151                    break;
152                default:
153                    // Shouldn't happen.
154                    Log.e(TAG, "received an unkown message : " + msg.what);
155            }
156        }
157
158        void handleStartScan(ScanClient client) {
159            Utils.enforceAdminPermission(mService);
160            logd("handling starting scan");
161
162            if (!isScanSupported(client)) {
163                Log.e(TAG, "Scan settings not supported");
164                return;
165            }
166
167            if (mRegularScanClients.contains(client) || mBatchClients.contains(client)) {
168                Log.e(TAG, "Scan already started");
169                return;
170            }
171            // Begin scan operations.
172            if (isBatchClient(client)) {
173                mBatchClients.add(client);
174                mScanNative.startBatchScan(client);
175            } else {
176                mRegularScanClients.add(client);
177                mScanNative.startRegularScan(client);
178            }
179        }
180
181        void handleStopScan(ScanClient client) {
182            Utils.enforceAdminPermission(mService);
183            if (mRegularScanClients.contains(client)) {
184                mRegularScanClients.remove(client);
185                mScanNative.stopRegularScan(client);
186            } else {
187                mBatchClients.remove(client);
188                mScanNative.stopBatchScan(client);
189            }
190        }
191
192        void handleFlushBatchResults(ScanClient client) {
193            Utils.enforceAdminPermission(mService);
194            if (!mBatchClients.contains(client)) {
195                return;
196            }
197            mScanNative.flushBatchResults(client.clientIf);
198        }
199
200        private boolean isBatchClient(ScanClient client) {
201            if (client == null || client.settings == null) {
202                return false;
203            }
204            ScanSettings settings = client.settings;
205            return settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ALL_MATCHES &&
206                    settings.getReportDelayMillis() != 0;
207        }
208
209        private boolean isScanSupported(ScanClient client) {
210            if (client == null || client.settings == null) {
211                return true;
212            }
213            ScanSettings settings = client.settings;
214            if (isFilteringSupported()) {
215                return true;
216            }
217            return settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ALL_MATCHES &&
218                    settings.getReportDelayMillis() == 0;
219        }
220    }
221
222    private class ScanNative {
223
224        // Delivery mode defined in bt stack.
225        private static final int DELIVERY_MODE_IMMEDIATE = 0;
226        private static final int DELIVERY_MODE_ON_FOUND = 1;
227        private static final int DELIVERY_MODE_BATCH = 2;
228
229        private static final int ALLOW_ALL_FILTER_INDEX = 1;
230        private static final int ALLOW_ALL_FILTER_SELECTION = 0;
231
232        // The logic is AND for each filter field.
233        private static final int LIST_LOGIC_TYPE = 0x1111111;
234        private static final int FILTER_LOGIC_TYPE = 1;
235        // Filter indices that are available to user. It's sad we need to maintain filter index.
236        private final Deque<Integer> mFilterIndexStack;
237        // Map of clientIf and Filter indices used by client.
238        private final Map<Integer, Deque<Integer>> mClientFilterIndexMap;
239
240        ScanNative() {
241            mFilterIndexStack = new ArrayDeque<Integer>();
242            mClientFilterIndexMap = new HashMap<Integer, Deque<Integer>>();
243        }
244
245        private void resetCountDownLatch() {
246            mLatch = new CountDownLatch(1);
247        }
248
249        // Returns true if mLatch reaches 0, false if timeout or interrupted.
250        private boolean waitForCallback() {
251            try {
252                return mLatch.await(OPERATION_TIME_OUT_MILLIS, TimeUnit.MILLISECONDS);
253            } catch (InterruptedException e) {
254                return false;
255            }
256        }
257
258        void startRegularScan(ScanClient client) {
259            if (mFilterIndexStack.isEmpty() && isFilteringSupported()) {
260                initFilterIndexStack();
261            }
262            if (isFilteringSupported()) {
263                configureScanFilters(client);
264            }
265            // Start scan native only for the first client.
266            if (mRegularScanClients.size() == 1) {
267                gattClientScanNative(true);
268            }
269        }
270
271        void startBatchScan(ScanClient client) {
272            if (mFilterIndexStack.isEmpty() && isFilteringSupported()) {
273                initFilterIndexStack();
274            }
275            configureScanFilters(client);
276            int fullScanPercent = 50;
277            int notifyThreshold = 95;
278            resetCountDownLatch();
279            logd("configuring batch scan storage, appIf " + client.clientIf);
280            gattClientConfigBatchScanStorageNative(client.clientIf, fullScanPercent,
281                    100 - fullScanPercent, notifyThreshold);
282            waitForCallback();
283            int scanMode = getResultType(client.settings);
284            // TODO: configure scan parameters.
285            int scanIntervalUnit = 8;
286            int scanWindowUnit = 8;
287            int discardRule = 2;
288            int addressType = 0;
289            logd("Starting BLE batch scan");
290            gattClientStartBatchScanNative(client.clientIf, scanMode, scanIntervalUnit, scanWindowUnit,
291                    addressType,
292                    discardRule);
293        }
294
295        void stopRegularScan(ScanClient client) {
296            // Remove scan filters and recycle filter indices.
297            removeScanFilters(client.clientIf);
298            mRegularScanClients.remove(client);
299            if (mRegularScanClients.isEmpty()) {
300                logd("stop scan");
301                gattClientScanNative(false);
302            }
303        }
304
305        void stopBatchScan(ScanClient client) {
306            removeScanFilters(client.clientIf);
307            mBatchClients.remove(client);
308            gattClientStopBatchScanNative(client.clientIf);
309        }
310
311        void flushBatchResults(int clientIf) {
312            logd("flushPendingBatchResults - clientIf = " + clientIf);
313            ScanClient client = getBatchScanClient(clientIf);
314            if (client == null) {
315                logd("unknown client : " + clientIf);
316                return;
317            }
318            int resultType = getResultType(client.settings);
319            gattClientReadScanReportsNative(client.clientIf, resultType);
320        }
321
322        // Add scan filters. The logic is:
323        // If no offload filter can/needs to be set, set ALLOW_ALL filter.
324        // Otherwise offload all filters to hardware and enable all filters.
325        private void configureScanFilters(ScanClient client) {
326            int clientIf = client.clientIf;
327            resetCountDownLatch();
328            gattClientScanFilterEnableNative(clientIf, true);
329            waitForCallback();
330
331            if (shouldUseAllowAllFilter(client)) {
332                resetCountDownLatch();
333                configureFilterParamter(clientIf, client, ALLOW_ALL_FILTER_SELECTION,
334                        ALLOW_ALL_FILTER_INDEX);
335                waitForCallback();
336            } else {
337                Deque<Integer> clientFilterIndices = new ArrayDeque<Integer>();
338                for (ScanFilter filter : client.filters) {
339                    ScanFilterQueue queue = new ScanFilterQueue();
340                    queue.addScanFilter(filter);
341                    int featureSelection = queue.getFeatureSelection();
342                    int filterIndex = mFilterIndexStack.pop();
343                    while (!queue.isEmpty()) {
344                        resetCountDownLatch();
345                        addFilterToController(clientIf, queue.pop(), filterIndex);
346                        waitForCallback();
347                    }
348                    resetCountDownLatch();
349                    configureFilterParamter(clientIf, client, featureSelection, filterIndex);
350                    waitForCallback();
351                    clientFilterIndices.add(filterIndex);
352                }
353                mClientFilterIndexMap.put(clientIf, clientFilterIndices);
354            }
355        }
356
357        private void removeScanFilters(int clientIf) {
358            logd("removeScanFilters, clientIf - " + clientIf);
359            Deque<Integer> filterIndices = mClientFilterIndexMap.remove(clientIf);
360            if (filterIndices != null) {
361                mFilterIndexStack.addAll(filterIndices);
362                for (Integer filterIndex : filterIndices) {
363                    resetCountDownLatch();
364                    gattClientScanFilterParamDeleteNative(clientIf, filterIndex);
365                    waitForCallback();
366                }
367            }
368        }
369
370        private ScanClient getBatchScanClient(int clientIf) {
371            for (ScanClient client : mBatchClients) {
372                if (client.clientIf == clientIf) {
373                    return client;
374                }
375            }
376            return null;
377        }
378
379        /**
380         * Return batch scan result type value defined in bt stack.
381         */
382        private int getResultType(ScanSettings settings) {
383            return settings.getScanResultType() == ScanSettings.SCAN_RESULT_TYPE_FULL ?
384                    SCAN_RESULT_TYPE_FULL : SCAN_RESULT_TYPE_TRUNCATED;
385        }
386
387        // Check if ALLOW_FILTER should be used for the client.
388        private boolean shouldUseAllowAllFilter(ScanClient client) {
389            if (client == null) {
390                return true;
391            }
392            if (client.filters == null || client.filters.isEmpty()) {
393                return true;
394            }
395            return client.filters.size() < mClientFilterIndexMap.size();
396        }
397
398        private void addFilterToController(int clientIf, ScanFilterQueue.Entry entry,
399                int filterIndex) {
400            logd("addFilterToController: " + entry.type);
401            switch (entry.type) {
402                case ScanFilterQueue.TYPE_DEVICE_ADDRESS:
403                    logd("add address " + entry.address);
404                    gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0,
405                            0,
406                            "", entry.address, (byte) 0, new byte[0], new byte[0]);
407                    break;
408
409                case ScanFilterQueue.TYPE_SERVICE_DATA:
410                    gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0,
411                            0,
412                            "", "", (byte) 0, entry.data, entry.data_mask);
413                    break;
414
415                case ScanFilterQueue.TYPE_SERVICE_UUID:
416                case ScanFilterQueue.TYPE_SOLICIT_UUID:
417                    gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0,
418                            entry.uuid.getLeastSignificantBits(),
419                            entry.uuid.getMostSignificantBits(),
420                            entry.uuid_mask.getLeastSignificantBits(),
421                            entry.uuid_mask.getMostSignificantBits(),
422                            "", "", (byte) 0, new byte[0], new byte[0]);
423                    break;
424
425                case ScanFilterQueue.TYPE_LOCAL_NAME:
426                    logd("adding filters: " + entry.name);
427                    gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0,
428                            0,
429                            entry.name, "", (byte) 0, new byte[0], new byte[0]);
430                    break;
431
432                case ScanFilterQueue.TYPE_MANUFACTURER_DATA:
433                {
434                    int len = entry.data.length;
435                    if (entry.data_mask.length != len)
436                        return;
437                    gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, entry.company,
438                            entry.company_mask, 0, 0, 0, 0, "", "", (byte) 0,
439                            entry.data, entry.data_mask);
440                    break;
441                }
442            }
443        }
444
445        private void initFilterIndexStack() {
446            int maxFiltersSupported =
447                    AdapterService.getAdapterService().getNumOfOffloadedScanFilterSupported();
448            // Start from index 2 as index 0 is reserved for ALLOW_ALL filter in Settings app and
449            // index 1 is reserved for ALLOW_ALL filter for regular apps.
450            for (int i = 2; i < maxFiltersSupported; ++i) {
451                mFilterIndexStack.add(i);
452            }
453        }
454
455        // Configure filter parameters.
456        private void configureFilterParamter(int clientIf, ScanClient client, int featureSelection,
457                int filterIndex) {
458            int deliveryMode = getDeliveryMode(client);
459            int rssiThreshold = Byte.MIN_VALUE;
460            gattClientScanFilterParamAddNative(
461                    clientIf, filterIndex, featureSelection, LIST_LOGIC_TYPE,
462                    FILTER_LOGIC_TYPE, rssiThreshold, rssiThreshold, deliveryMode,
463                    0, 0, 0);
464        }
465
466        // Get delivery mode based on scan settings.
467        private int getDeliveryMode(ScanClient client) {
468            if (client == null) {
469                return DELIVERY_MODE_IMMEDIATE;
470            }
471            ScanSettings settings = client.settings;
472            if (settings == null) {
473                return DELIVERY_MODE_IMMEDIATE;
474            }
475            // TODO: double check whether it makes sense to use the same delivery mode for found and
476            // lost.
477            if ((settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0
478                    || (settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_MATCH_LOST) != 0) {
479                return DELIVERY_MODE_ON_FOUND;
480            }
481            return settings.getReportDelayMillis() == 0 ? DELIVERY_MODE_IMMEDIATE
482                    : DELIVERY_MODE_BATCH;
483        }
484
485        /************************** Regular scan related native methods **************************/
486        private native void gattClientScanNative(boolean start);
487
488        /************************** Filter related native methods ********************************/
489        private native void gattClientScanFilterAddNative(int client_if,
490                int filter_type, int filter_index, int company_id,
491                int company_id_mask, long uuid_lsb, long uuid_msb,
492                long uuid_mask_lsb, long uuid_mask_msb, String name,
493                String address, byte addr_type, byte[] data, byte[] mask);
494
495        private native void gattClientScanFilterDeleteNative(int client_if,
496                int filter_type, int filter_index, int company_id,
497                int company_id_mask, long uuid_lsb, long uuid_msb,
498                long uuid_mask_lsb, long uuid_mask_msb, String name,
499                String address, byte addr_type, byte[] data, byte[] mask);
500
501        private native void gattClientScanFilterParamAddNative(
502                int client_if, int filt_index, int feat_seln,
503                int list_logic_type, int filt_logic_type, int rssi_high_thres,
504                int rssi_low_thres, int dely_mode, int found_timeout,
505                int lost_timeout, int found_timeout_cnt);
506
507        // Note this effectively remove scan filters for ALL clients.
508        private native void gattClientScanFilterParamClearAllNative(
509                int client_if);
510
511        private native void gattClientScanFilterParamDeleteNative(
512                int client_if, int filt_index);
513
514        private native void gattClientScanFilterClearNative(int client_if,
515                int filter_index);
516
517        private native void gattClientScanFilterEnableNative(int client_if,
518                boolean enable);
519
520        /************************** Batch related native methods *********************************/
521        private native void gattClientConfigBatchScanStorageNative(int client_if,
522                int max_full_reports_percent, int max_truncated_reports_percent,
523                int notify_threshold_percent);
524
525        private native void gattClientStartBatchScanNative(int client_if, int scan_mode,
526                int scan_interval_unit, int scan_window_unit, int address_type, int discard_rule);
527
528        private native void gattClientStopBatchScanNative(int client_if);
529
530        private native void gattClientReadScanReportsNative(int client_if, int scan_type);
531    }
532
533    private void logd(String s) {
534        Log.d(TAG, s);
535    }
536}
537