ScanManager.java revision 12adb6a704e8f484cdd0004edd44edeed2c37890
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.app.AlarmManager;
20import android.app.PendingIntent;
21import android.bluetooth.BluetoothAdapter;
22import android.bluetooth.le.ScanCallback;
23import android.bluetooth.le.ScanFilter;
24import android.bluetooth.le.ScanSettings;
25import android.content.BroadcastReceiver;
26import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.os.Handler;
30import android.os.HandlerThread;
31import android.os.Looper;
32import android.os.Message;
33import android.os.RemoteException;
34import android.os.SystemClock;
35import android.util.Log;
36
37import com.android.bluetooth.Utils;
38import com.android.bluetooth.btservice.AdapterService;
39
40import java.util.ArrayDeque;
41import java.util.Deque;
42import java.util.HashMap;
43import java.util.HashSet;
44import java.util.Map;
45import java.util.Set;
46import java.util.concurrent.CountDownLatch;
47import java.util.concurrent.TimeUnit;
48
49/**
50 * Class that handles Bluetooth LE scan related operations.
51 *
52 * @hide
53 */
54public class ScanManager {
55    private static final boolean DBG = GattServiceConfig.DBG;
56    private static final String TAG = GattServiceConfig.TAG_PREFIX + "ScanManager";
57
58    // Result type defined in bt stack. Need to be accessed by GattService.
59    static final int SCAN_RESULT_TYPE_TRUNCATED = 1;
60    static final int SCAN_RESULT_TYPE_FULL = 2;
61    static final int SCAN_RESULT_TYPE_BOTH = 3;
62
63    // Internal messages for handling BLE scan operations.
64    private static final int MSG_START_BLE_SCAN = 0;
65    private static final int MSG_STOP_BLE_SCAN = 1;
66    private static final int MSG_FLUSH_BATCH_RESULTS = 2;
67
68    private static final String ACTION_REFRESH_BATCHED_SCAN =
69            "com.android.bluetooth.gatt.REFRESH_BATCHED_SCAN";
70
71    // Timeout for each controller operation.
72    private static final int OPERATION_TIME_OUT_MILLIS = 500;
73
74    private int mLastConfiguredScanSetting = Integer.MIN_VALUE;
75    // Scan parameters for batch scan.
76    private BatchScanParams mBatchScanParms;
77
78    private Integer curUsedTrackableAdvertisements;
79    private GattService mService;
80    private BroadcastReceiver mBatchAlarmReceiver;
81    private boolean mBatchAlarmReceiverRegistered;
82    private ScanNative mScanNative;
83    private ClientHandler mHandler;
84
85    private Set<ScanClient> mRegularScanClients;
86    private Set<ScanClient> mBatchClients;
87
88    private CountDownLatch mLatch;
89
90    ScanManager(GattService service) {
91        mRegularScanClients = new HashSet<ScanClient>();
92        mBatchClients = new HashSet<ScanClient>();
93        mService = service;
94        mScanNative = new ScanNative();
95        curUsedTrackableAdvertisements = 0;
96    }
97
98    void start() {
99        HandlerThread thread = new HandlerThread("BluetoothScanManager");
100        thread.start();
101        mHandler = new ClientHandler(thread.getLooper());
102    }
103
104    void cleanup() {
105        mRegularScanClients.clear();
106        mBatchClients.clear();
107        mScanNative.cleanup();
108    }
109
110    /**
111     * Returns the regular scan queue.
112     */
113    Set<ScanClient> getRegularScanQueue() {
114        return mRegularScanClients;
115    }
116
117    /**
118     * Returns batch scan queue.
119     */
120    Set<ScanClient> getBatchScanQueue() {
121        return mBatchClients;
122    }
123
124    /**
125     * Returns a set of full batch scan clients.
126     */
127    Set<ScanClient> getFullBatchScanQueue() {
128        // TODO: split full batch scan clients and truncated batch clients so we don't need to
129        // construct this every time.
130        Set<ScanClient> fullBatchClients = new HashSet<ScanClient>();
131        for (ScanClient client : mBatchClients) {
132            if (client.settings.getScanResultType() == ScanSettings.SCAN_RESULT_TYPE_FULL) {
133                fullBatchClients.add(client);
134            }
135        }
136        return fullBatchClients;
137    }
138
139    void startScan(ScanClient client) {
140        sendMessage(MSG_START_BLE_SCAN, client);
141    }
142
143    void stopScan(ScanClient client) {
144        sendMessage(MSG_STOP_BLE_SCAN, client);
145    }
146
147    void flushBatchScanResults(ScanClient client) {
148        sendMessage(MSG_FLUSH_BATCH_RESULTS, client);
149    }
150
151    void callbackDone(int clientIf, int status) {
152        logd("callback done for clientIf - " + clientIf + " status - " + status);
153        if (status == 0) {
154            mLatch.countDown();
155        }
156        // TODO: add a callback for scan failure.
157    }
158
159    private void sendMessage(int what, ScanClient client) {
160        Message message = new Message();
161        message.what = what;
162        message.obj = client;
163        mHandler.sendMessage(message);
164    }
165
166    private boolean isFilteringSupported() {
167        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
168        return adapter.isOffloadedFilteringSupported();
169    }
170
171    // Handler class that handles BLE scan operations.
172    private class ClientHandler extends Handler {
173
174        ClientHandler(Looper looper) {
175            super(looper);
176        }
177
178        @Override
179        public void handleMessage(Message msg) {
180            ScanClient client = (ScanClient) msg.obj;
181            switch (msg.what) {
182                case MSG_START_BLE_SCAN:
183                    handleStartScan(client);
184                    break;
185                case MSG_STOP_BLE_SCAN:
186                    handleStopScan(client);
187                    break;
188                case MSG_FLUSH_BATCH_RESULTS:
189                    handleFlushBatchResults(client);
190                    break;
191                default:
192                    // Shouldn't happen.
193                    Log.e(TAG, "received an unkown message : " + msg.what);
194            }
195        }
196
197        void handleStartScan(ScanClient client) {
198            Utils.enforceAdminPermission(mService);
199            logd("handling starting scan");
200
201            if (!isScanSupported(client)) {
202                Log.e(TAG, "Scan settings not supported");
203                return;
204            }
205
206            if (mRegularScanClients.contains(client) || mBatchClients.contains(client)) {
207                Log.e(TAG, "Scan already started");
208                return;
209            }
210            // Begin scan operations.
211            if (isBatchClient(client)) {
212                mBatchClients.add(client);
213                mScanNative.startBatchScan(client);
214            } else {
215                mRegularScanClients.add(client);
216                mScanNative.startRegularScan(client);
217                if (!mScanNative.isOpportunisticScanClient(client)) {
218                    mScanNative.configureRegularScanParams();
219                }
220            }
221        }
222
223        void handleStopScan(ScanClient client) {
224            Utils.enforceAdminPermission(mService);
225            if (client == null) return;
226            if (mRegularScanClients.contains(client)) {
227                mScanNative.stopRegularScan(client);
228                if (!mScanNative.isOpportunisticScanClient(client)) {
229                    mScanNative.configureRegularScanParams();
230                }
231            } else {
232                mScanNative.stopBatchScan(client);
233            }
234            if (client.appDied) {
235                logd("app died, unregister client - " + client.clientIf);
236                mService.unregisterClient(client.clientIf);
237            }
238        }
239
240        void handleFlushBatchResults(ScanClient client) {
241            Utils.enforceAdminPermission(mService);
242            if (!mBatchClients.contains(client)) {
243                return;
244            }
245            mScanNative.flushBatchResults(client.clientIf);
246        }
247
248        private boolean isBatchClient(ScanClient client) {
249            if (client == null || client.settings == null) {
250                return false;
251            }
252            ScanSettings settings = client.settings;
253            return settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ALL_MATCHES &&
254                    settings.getReportDelayMillis() != 0;
255        }
256
257        private boolean isScanSupported(ScanClient client) {
258            if (client == null || client.settings == null) {
259                return true;
260            }
261            ScanSettings settings = client.settings;
262            if (isFilteringSupported()) {
263                return true;
264            }
265            return settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ALL_MATCHES &&
266                    settings.getReportDelayMillis() == 0;
267        }
268    }
269
270    /**
271     * Parameters for batch scans.
272     */
273    class BatchScanParams {
274        int scanMode;
275        int fullScanClientIf;
276        int truncatedScanClientIf;
277
278        BatchScanParams() {
279            scanMode = -1;
280            fullScanClientIf = -1;
281            truncatedScanClientIf = -1;
282        }
283
284        @Override
285        public boolean equals(Object obj) {
286            if (this == obj) {
287                return true;
288            }
289            if (obj == null || getClass() != obj.getClass()) {
290                return false;
291            }
292            BatchScanParams other = (BatchScanParams) obj;
293            return scanMode == other.scanMode && fullScanClientIf == other.fullScanClientIf
294                    && truncatedScanClientIf == other.truncatedScanClientIf;
295
296        }
297    }
298
299    public int getCurrentUsedTrackingAdvertisement() {
300        return curUsedTrackableAdvertisements;
301    }
302
303    private class ScanNative {
304
305        // Delivery mode defined in bt stack.
306        private static final int DELIVERY_MODE_IMMEDIATE = 0;
307        private static final int DELIVERY_MODE_ON_FOUND_LOST = 1;
308        private static final int DELIVERY_MODE_BATCH = 2;
309
310        private static final int ONFOUND_SIGHTINGS_AGGRESSIVE = 2;
311        private static final int ONFOUND_SIGHTINGS_STICKY = 5;
312
313        private static final int ALL_PASS_FILTER_INDEX_REGULAR_SCAN = 1;
314        private static final int ALL_PASS_FILTER_INDEX_BATCH_SCAN = 2;
315        private static final int ALL_PASS_FILTER_SELECTION = 0;
316
317        private static final int DISCARD_OLDEST_WHEN_BUFFER_FULL = 0;
318
319        /**
320         * Scan params corresponding to regular scan setting
321         */
322        private static final int SCAN_MODE_LOW_POWER_WINDOW_MS = 500;
323        private static final int SCAN_MODE_LOW_POWER_INTERVAL_MS = 5000;
324        private static final int SCAN_MODE_BALANCED_WINDOW_MS = 2000;
325        private static final int SCAN_MODE_BALANCED_INTERVAL_MS = 5000;
326        private static final int SCAN_MODE_LOW_LATENCY_WINDOW_MS = 5000;
327        private static final int SCAN_MODE_LOW_LATENCY_INTERVAL_MS = 5000;
328
329        /**
330         * Onfound/onlost timeouts are factors of scan settings
331         * To scan the three advertisement channels, it requires 3 scan
332         * intervals
333         * ToDo: Tuning these values for the platform
334         */
335        private static final int MATCH_MODE_AGGRESSIVE_TIMEOUT_FACTOR = (3*1);
336        private static final int MATCH_MODE_STICKY_TIMEOUT_FACTOR = (3*5);
337        private static final int ONLOST_FACTOR = 2;
338
339        /**
340         * Scan params corresponding to batch scan setting
341         */
342        private static final int SCAN_MODE_BATCH_LOW_POWER_WINDOW_MS = 1500;
343        private static final int SCAN_MODE_BATCH_LOW_POWER_INTERVAL_MS = 150000;
344        private static final int SCAN_MODE_BATCH_BALANCED_WINDOW_MS = 1500;
345        private static final int SCAN_MODE_BATCH_BALANCED_INTERVAL_MS = 15000;
346        private static final int SCAN_MODE_BATCH_LOW_LATENCY_WINDOW_MS = 1500;
347        private static final int SCAN_MODE_BATCH_LOW_LATENCY_INTERVAL_MS = 5000;
348
349        // The logic is AND for each filter field.
350        private static final int LIST_LOGIC_TYPE = 0x1111111;
351        private static final int FILTER_LOGIC_TYPE = 1;
352        // Filter indices that are available to user. It's sad we need to maintain filter index.
353        private final Deque<Integer> mFilterIndexStack;
354        // Map of clientIf and Filter indices used by client.
355        private final Map<Integer, Deque<Integer>> mClientFilterIndexMap;
356        // Keep track of the clients that uses ALL_PASS filters.
357        private final Set<Integer> mAllPassRegularClients = new HashSet<>();
358        private final Set<Integer> mAllPassBatchClients = new HashSet<>();
359
360        private AlarmManager mAlarmManager;
361        private PendingIntent mBatchScanIntervalIntent;
362
363        ScanNative() {
364            mFilterIndexStack = new ArrayDeque<Integer>();
365            mClientFilterIndexMap = new HashMap<Integer, Deque<Integer>>();
366
367            mAlarmManager = (AlarmManager) mService.getSystemService(Context.ALARM_SERVICE);
368            Intent batchIntent = new Intent(ACTION_REFRESH_BATCHED_SCAN, null);
369            mBatchScanIntervalIntent = PendingIntent.getBroadcast(mService, 0, batchIntent, 0);
370            IntentFilter filter = new IntentFilter();
371            filter.addAction(ACTION_REFRESH_BATCHED_SCAN);
372            mBatchAlarmReceiver = new BroadcastReceiver() {
373                    @Override
374                public void onReceive(Context context, Intent intent) {
375                    Log.d(TAG, "awakened up at time " + SystemClock.elapsedRealtime());
376                    String action = intent.getAction();
377
378                    if (action.equals(ACTION_REFRESH_BATCHED_SCAN)) {
379                        if (mBatchClients.isEmpty()) {
380                            return;
381                        }
382                        // Note this actually flushes all pending batch data.
383                        flushBatchScanResults(mBatchClients.iterator().next());
384                    }
385                }
386            };
387            mService.registerReceiver(mBatchAlarmReceiver, filter);
388            mBatchAlarmReceiverRegistered = true;
389        }
390
391        private void resetCountDownLatch() {
392            mLatch = new CountDownLatch(1);
393        }
394
395        // Returns true if mLatch reaches 0, false if timeout or interrupted.
396        private boolean waitForCallback() {
397            try {
398                return mLatch.await(OPERATION_TIME_OUT_MILLIS, TimeUnit.MILLISECONDS);
399            } catch (InterruptedException e) {
400                return false;
401            }
402        }
403
404        void configureRegularScanParams() {
405            logd("configureRegularScanParams() - queue=" + mRegularScanClients.size());
406            int curScanSetting = Integer.MIN_VALUE;
407            ScanClient client = getAggressiveClient(mRegularScanClients);
408            if (client != null) {
409                curScanSetting = client.settings.getScanMode();
410            }
411
412            logd("configureRegularScanParams() - ScanSetting Scan mode=" + curScanSetting +
413                    " mLastConfiguredScanSetting=" + mLastConfiguredScanSetting);
414
415            if (curScanSetting != Integer.MIN_VALUE &&
416                    curScanSetting != ScanSettings.SCAN_MODE_OPPORTUNISTIC) {
417                if (curScanSetting != mLastConfiguredScanSetting) {
418                    int scanWindow = getScanWindowMillis(client.settings);
419                    int scanInterval = getScanIntervalMillis(client.settings);
420                    // convert scanWindow and scanInterval from ms to LE scan units(0.625ms)
421                    scanWindow = Utils.millsToUnit(scanWindow);
422                    scanInterval = Utils.millsToUnit(scanInterval);
423                    gattClientScanNative(false);
424                    gattSetScanParametersNative(scanInterval, scanWindow);
425                    gattClientScanNative(true);
426                    mLastConfiguredScanSetting = curScanSetting;
427                }
428            } else {
429                mLastConfiguredScanSetting = curScanSetting;
430                logd("configureRegularScanParams() - queue emtpy, scan stopped");
431            }
432        }
433
434        ScanClient getAggressiveClient(Set<ScanClient> cList) {
435            ScanClient result = null;
436            int curScanSetting = Integer.MIN_VALUE;
437            for (ScanClient client : cList) {
438                // ScanClient scan settings are assumed to be monotonically increasing in value for
439                // more power hungry(higher duty cycle) operation.
440                if (client.settings.getScanMode() > curScanSetting) {
441                    result = client;
442                    curScanSetting = client.settings.getScanMode();
443                }
444            }
445            return result;
446        }
447
448        void startRegularScan(ScanClient client) {
449            if (isFilteringSupported() && mFilterIndexStack.isEmpty()
450                    && mClientFilterIndexMap.isEmpty()) {
451                initFilterIndexStack();
452            }
453            if (isFilteringSupported()) {
454                configureScanFilters(client);
455            }
456            // Start scan native only for the first client.
457            if (numRegularScanClients() == 1) {
458                gattClientScanNative(true);
459            }
460        }
461
462        private int numRegularScanClients() {
463            int num = 0;
464            for (ScanClient client: mRegularScanClients) {
465                if (client.settings.getScanMode() != ScanSettings.SCAN_MODE_OPPORTUNISTIC) {
466                    num++;
467                }
468            }
469            return num;
470        }
471
472        void startBatchScan(ScanClient client) {
473            if (mFilterIndexStack.isEmpty() && isFilteringSupported()) {
474                initFilterIndexStack();
475            }
476            configureScanFilters(client);
477            if (!isOpportunisticScanClient(client)) {
478                // Reset batch scan. May need to stop the existing batch scan and update scan params.
479                resetBatchScan(client);
480            }
481        }
482
483        private boolean isOpportunisticScanClient(ScanClient client) {
484            return client.settings.getScanMode() == ScanSettings.SCAN_MODE_OPPORTUNISTIC;
485        }
486
487        private void resetBatchScan(ScanClient client) {
488            int clientIf = client.clientIf;
489            BatchScanParams batchScanParams = getBatchScanParams();
490            // Stop batch if batch scan params changed and previous params is not null.
491            if (mBatchScanParms != null && (!mBatchScanParms.equals(batchScanParams))) {
492                logd("stopping BLe Batch");
493                resetCountDownLatch();
494                gattClientStopBatchScanNative(clientIf);
495                waitForCallback();
496                // Clear pending results as it's illegal to config storage if there are still
497                // pending results.
498                flushBatchResults(clientIf);
499            }
500            // Start batch if batchScanParams changed and current params is not null.
501            if (batchScanParams != null && (!batchScanParams.equals(mBatchScanParms))) {
502                int notifyThreshold = 95;
503                logd("Starting BLE batch scan");
504                int resultType = getResultType(batchScanParams);
505                int fullScanPercent = getFullScanStoragePercent(resultType);
506                resetCountDownLatch();
507                logd("configuring batch scan storage, appIf " + client.clientIf);
508                gattClientConfigBatchScanStorageNative(client.clientIf, fullScanPercent,
509                        100 - fullScanPercent, notifyThreshold);
510                waitForCallback();
511                resetCountDownLatch();
512                int scanInterval =
513                        Utils.millsToUnit(getBatchScanIntervalMillis(batchScanParams.scanMode));
514                int scanWindow =
515                        Utils.millsToUnit(getBatchScanWindowMillis(batchScanParams.scanMode));
516                gattClientStartBatchScanNative(clientIf, resultType, scanInterval,
517                        scanWindow, 0, DISCARD_OLDEST_WHEN_BUFFER_FULL);
518                waitForCallback();
519            }
520            mBatchScanParms = batchScanParams;
521            setBatchAlarm();
522        }
523
524        private int getFullScanStoragePercent(int resultType) {
525            switch (resultType) {
526                case SCAN_RESULT_TYPE_FULL:
527                    return 100;
528                case SCAN_RESULT_TYPE_TRUNCATED:
529                    return 0;
530                case SCAN_RESULT_TYPE_BOTH:
531                    return 50;
532                default:
533                    return 50;
534            }
535        }
536
537        private BatchScanParams getBatchScanParams() {
538            if (mBatchClients.isEmpty()) {
539                return null;
540            }
541            BatchScanParams params = new BatchScanParams();
542            // TODO: split full batch scan results and truncated batch scan results to different
543            // collections.
544            for (ScanClient client : mBatchClients) {
545                params.scanMode = Math.max(params.scanMode, client.settings.getScanMode());
546                if (client.settings.getScanResultType() == ScanSettings.SCAN_RESULT_TYPE_FULL) {
547                    params.fullScanClientIf = client.clientIf;
548                } else {
549                    params.truncatedScanClientIf = client.clientIf;
550                }
551            }
552            return params;
553        }
554
555        private int getBatchScanWindowMillis(int scanMode) {
556            switch (scanMode) {
557                case ScanSettings.SCAN_MODE_LOW_LATENCY:
558                    return SCAN_MODE_BATCH_LOW_LATENCY_WINDOW_MS;
559                case ScanSettings.SCAN_MODE_BALANCED:
560                    return SCAN_MODE_BATCH_BALANCED_WINDOW_MS;
561                case ScanSettings.SCAN_MODE_LOW_POWER:
562                    return SCAN_MODE_BATCH_LOW_POWER_WINDOW_MS;
563                default:
564                    return SCAN_MODE_BATCH_LOW_POWER_WINDOW_MS;
565            }
566        }
567
568        private int getBatchScanIntervalMillis(int scanMode) {
569            switch (scanMode) {
570                case ScanSettings.SCAN_MODE_LOW_LATENCY:
571                    return SCAN_MODE_BATCH_LOW_LATENCY_INTERVAL_MS;
572                case ScanSettings.SCAN_MODE_BALANCED:
573                    return SCAN_MODE_BATCH_BALANCED_INTERVAL_MS;
574                case ScanSettings.SCAN_MODE_LOW_POWER:
575                    return SCAN_MODE_BATCH_LOW_POWER_INTERVAL_MS;
576                default:
577                    return SCAN_MODE_BATCH_LOW_POWER_INTERVAL_MS;
578            }
579        }
580
581        // Set the batch alarm to be triggered within a short window after batch interval. This
582        // allows system to optimize wake up time while still allows a degree of precise control.
583        private void setBatchAlarm() {
584            // Cancel any pending alarm just in case.
585            mAlarmManager.cancel(mBatchScanIntervalIntent);
586            if (mBatchClients.isEmpty()) {
587                return;
588            }
589            long batchTriggerIntervalMillis = getBatchTriggerIntervalMillis();
590            // Allows the alarm to be triggered within
591            // [batchTriggerIntervalMillis, 1.1 * batchTriggerIntervalMillis]
592            long windowLengthMillis = batchTriggerIntervalMillis / 10;
593            long windowStartMillis = SystemClock.elapsedRealtime() + batchTriggerIntervalMillis;
594            mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP,
595                    windowStartMillis, windowLengthMillis,
596                    mBatchScanIntervalIntent);
597        }
598
599        void stopRegularScan(ScanClient client) {
600            // Remove scan filters and recycle filter indices.
601            client = getClient(client.clientIf);
602            if (client == null) return;
603            int deliveryMode = getDeliveryMode(client);
604            if (deliveryMode == DELIVERY_MODE_ON_FOUND_LOST) {
605                for (ScanFilter filter : client.filters) {
606                    int entriesToFree = getNumOfTrackingAdvertisements(client.settings);
607                    if (!manageAllocationOfTrackingAdvertisement(entriesToFree, false)) {
608                        Log.e(TAG, "Error freeing for onfound/onlost filter resources "
609                                    + entriesToFree);
610                        try {
611                            mService.onScanManagerErrorCallback(client.clientIf,
612                                            ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
613                        } catch (RemoteException e) {
614                            Log.e(TAG, "failed on onScanManagerCallback at freeing", e);
615                        }
616                    }
617                }
618            }
619            mRegularScanClients.remove(client);
620            if (numRegularScanClients() == 0) {
621                logd("stop scan");
622                gattClientScanNative(false);
623            }
624            removeScanFilters(client.clientIf);
625        }
626
627        // Find the scan client information
628        ScanClient getClient(int clientIf) {
629            for (ScanClient client : mRegularScanClients) {
630              if (client.clientIf == clientIf) return client;
631            }
632            return null;
633        }
634
635        void stopBatchScan(ScanClient client) {
636            mBatchClients.remove(client);
637            removeScanFilters(client.clientIf);
638            if (!isOpportunisticScanClient(client)) {
639                resetBatchScan(client);
640            }
641        }
642
643        void flushBatchResults(int clientIf) {
644            logd("flushPendingBatchResults - clientIf = " + clientIf);
645            if (mBatchScanParms.fullScanClientIf != -1) {
646                resetCountDownLatch();
647                gattClientReadScanReportsNative(mBatchScanParms.fullScanClientIf,
648                        SCAN_RESULT_TYPE_FULL);
649                waitForCallback();
650            }
651            if (mBatchScanParms.truncatedScanClientIf != -1) {
652                resetCountDownLatch();
653                gattClientReadScanReportsNative(mBatchScanParms.truncatedScanClientIf,
654                        SCAN_RESULT_TYPE_TRUNCATED);
655                waitForCallback();
656            }
657            setBatchAlarm();
658        }
659
660        void cleanup() {
661            mAlarmManager.cancel(mBatchScanIntervalIntent);
662            // Protect against multiple calls of cleanup.
663            if (mBatchAlarmReceiverRegistered) {
664                mService.unregisterReceiver(mBatchAlarmReceiver);
665            }
666            mBatchAlarmReceiverRegistered = false;
667        }
668
669        private long getBatchTriggerIntervalMillis() {
670            long intervalMillis = Long.MAX_VALUE;
671            for (ScanClient client : mBatchClients) {
672                if (client.settings != null && client.settings.getReportDelayMillis() > 0) {
673                    intervalMillis = Math.min(intervalMillis,
674                            client.settings.getReportDelayMillis());
675                }
676            }
677            return intervalMillis;
678        }
679
680        // Add scan filters. The logic is:
681        // If no offload filter can/needs to be set, set ALL_PASS filter.
682        // Otherwise offload all filters to hardware and enable all filters.
683        private void configureScanFilters(ScanClient client) {
684            int clientIf = client.clientIf;
685            int deliveryMode = getDeliveryMode(client);
686            int trackEntries = 0;
687            if (!shouldAddAllPassFilterToController(client, deliveryMode)) {
688                return;
689            }
690
691            resetCountDownLatch();
692            gattClientScanFilterEnableNative(clientIf, true);
693            waitForCallback();
694
695            if (shouldUseAllPassFilter(client)) {
696                int filterIndex = (deliveryMode == DELIVERY_MODE_BATCH) ?
697                        ALL_PASS_FILTER_INDEX_BATCH_SCAN : ALL_PASS_FILTER_INDEX_REGULAR_SCAN;
698                resetCountDownLatch();
699                // Don't allow Onfound/onlost with all pass
700                configureFilterParamter(clientIf, client, ALL_PASS_FILTER_SELECTION,
701                                filterIndex, 0);
702                waitForCallback();
703            } else {
704                Deque<Integer> clientFilterIndices = new ArrayDeque<Integer>();
705                for (ScanFilter filter : client.filters) {
706                    ScanFilterQueue queue = new ScanFilterQueue();
707                    queue.addScanFilter(filter);
708                    int featureSelection = queue.getFeatureSelection();
709                    int filterIndex = mFilterIndexStack.pop();
710                    while (!queue.isEmpty()) {
711                        resetCountDownLatch();
712                        addFilterToController(clientIf, queue.pop(), filterIndex);
713                        waitForCallback();
714                    }
715                    resetCountDownLatch();
716                    if (deliveryMode == DELIVERY_MODE_ON_FOUND_LOST) {
717                        trackEntries = getNumOfTrackingAdvertisements(client.settings);
718                        if (!manageAllocationOfTrackingAdvertisement(trackEntries, true)) {
719                            Log.e(TAG, "No hardware resources for onfound/onlost filter " +
720                                    trackEntries);
721                            try {
722                                mService.onScanManagerErrorCallback(clientIf,
723                                            ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
724                            } catch (RemoteException e) {
725                                Log.e(TAG, "failed on onScanManagerCallback", e);
726                            }
727                        }
728                    }
729                    configureFilterParamter(clientIf, client, featureSelection, filterIndex,
730                                            trackEntries);
731                    waitForCallback();
732                    clientFilterIndices.add(filterIndex);
733                }
734                mClientFilterIndexMap.put(clientIf, clientFilterIndices);
735            }
736        }
737
738        // Check whether the filter should be added to controller.
739        // Note only on ALL_PASS filter should be added.
740        private boolean shouldAddAllPassFilterToController(ScanClient client, int deliveryMode) {
741            // Not an ALL_PASS client, need to add filter.
742            if (!shouldUseAllPassFilter(client)) {
743                return true;
744            }
745
746            if (deliveryMode == DELIVERY_MODE_BATCH) {
747                mAllPassBatchClients.add(client.clientIf);
748                return mAllPassBatchClients.size() == 1;
749            } else {
750                mAllPassRegularClients.add(client.clientIf);
751                return mAllPassRegularClients.size() == 1;
752            }
753        }
754
755        private void removeScanFilters(int clientIf) {
756            Deque<Integer> filterIndices = mClientFilterIndexMap.remove(clientIf);
757            if (filterIndices != null) {
758                mFilterIndexStack.addAll(filterIndices);
759                for (Integer filterIndex : filterIndices) {
760                    resetCountDownLatch();
761                    gattClientScanFilterParamDeleteNative(clientIf, filterIndex);
762                    waitForCallback();
763                }
764            }
765            // Remove if ALL_PASS filters are used.
766            removeFilterIfExisits(mAllPassRegularClients, clientIf,
767                    ALL_PASS_FILTER_INDEX_REGULAR_SCAN);
768            removeFilterIfExisits(mAllPassBatchClients, clientIf,
769                    ALL_PASS_FILTER_INDEX_BATCH_SCAN);
770        }
771
772        private void removeFilterIfExisits(Set<Integer> clients, int clientIf, int filterIndex) {
773            if (!clients.contains(clientIf)) {
774                return;
775            }
776            clients.remove(clientIf);
777            // Remove ALL_PASS filter iff no app is using it.
778            if (clients.isEmpty()) {
779                resetCountDownLatch();
780                gattClientScanFilterParamDeleteNative(clientIf, filterIndex);
781                waitForCallback();
782            }
783        }
784
785        private ScanClient getBatchScanClient(int clientIf) {
786            for (ScanClient client : mBatchClients) {
787                if (client.clientIf == clientIf) {
788                    return client;
789                }
790            }
791            return null;
792        }
793
794        /**
795         * Return batch scan result type value defined in bt stack.
796         */
797        private int getResultType(BatchScanParams params) {
798            if (params.fullScanClientIf != -1 && params.truncatedScanClientIf != -1) {
799                return SCAN_RESULT_TYPE_BOTH;
800            }
801            if (params.truncatedScanClientIf != -1) {
802                return SCAN_RESULT_TYPE_TRUNCATED;
803            }
804            if (params.fullScanClientIf != -1) {
805                return SCAN_RESULT_TYPE_FULL;
806            }
807            return -1;
808        }
809
810        // Check if ALL_PASS filter should be used for the client.
811        private boolean shouldUseAllPassFilter(ScanClient client) {
812            if (client == null) {
813                return true;
814            }
815            if (client.filters == null || client.filters.isEmpty()) {
816                return true;
817            }
818            return client.filters.size() > mFilterIndexStack.size();
819        }
820
821        private void addFilterToController(int clientIf, ScanFilterQueue.Entry entry,
822                int filterIndex) {
823            logd("addFilterToController: " + entry.type);
824            switch (entry.type) {
825                case ScanFilterQueue.TYPE_DEVICE_ADDRESS:
826                    logd("add address " + entry.address);
827                    gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0,
828                            0,
829                            "", entry.address, (byte) 0, new byte[0], new byte[0]);
830                    break;
831
832                case ScanFilterQueue.TYPE_SERVICE_DATA:
833                    gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0,
834                            0,
835                            "", "", (byte) 0, entry.data, entry.data_mask);
836                    break;
837
838                case ScanFilterQueue.TYPE_SERVICE_UUID:
839                case ScanFilterQueue.TYPE_SOLICIT_UUID:
840                    gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0,
841                            entry.uuid.getLeastSignificantBits(),
842                            entry.uuid.getMostSignificantBits(),
843                            entry.uuid_mask.getLeastSignificantBits(),
844                            entry.uuid_mask.getMostSignificantBits(),
845                            "", "", (byte) 0, new byte[0], new byte[0]);
846                    break;
847
848                case ScanFilterQueue.TYPE_LOCAL_NAME:
849                    logd("adding filters: " + entry.name);
850                    gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0,
851                            0,
852                            entry.name, "", (byte) 0, new byte[0], new byte[0]);
853                    break;
854
855                case ScanFilterQueue.TYPE_MANUFACTURER_DATA:
856                    int len = entry.data.length;
857                    if (entry.data_mask.length != len)
858                        return;
859                    gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, entry.company,
860                            entry.company_mask, 0, 0, 0, 0, "", "", (byte) 0,
861                            entry.data, entry.data_mask);
862                    break;
863            }
864        }
865
866        private void initFilterIndexStack() {
867            int maxFiltersSupported =
868                    AdapterService.getAdapterService().getNumOfOffloadedScanFilterSupported();
869            // Start from index 3 as:
870            // index 0 is reserved for ALL_PASS filter in Settings app.
871            // index 1 is reserved for ALL_PASS filter for regular scan apps.
872            // index 2 is reserved for ALL_PASS filter for batch scan apps.
873            for (int i = 3; i < maxFiltersSupported; ++i) {
874                mFilterIndexStack.add(i);
875            }
876        }
877
878        // Configure filter parameters.
879        private void configureFilterParamter(int clientIf, ScanClient client, int featureSelection,
880                int filterIndex, int numOfTrackingEntries) {
881            int deliveryMode = getDeliveryMode(client);
882            int rssiThreshold = Byte.MIN_VALUE;
883            ScanSettings settings = client.settings;
884            int onFoundTimeout = getOnFoundOnLostTimeoutMillis(settings, true);
885            int onLostTimeout = getOnFoundOnLostTimeoutMillis(settings, false);
886            int onFoundCount = getOnFoundOnLostSightings(settings);
887            logd("configureFilterParamter " + onFoundTimeout + " " + onLostTimeout + " "
888                    + onFoundCount + " " + numOfTrackingEntries);
889            FilterParams FiltValue = new FilterParams(clientIf, filterIndex, featureSelection,
890                    LIST_LOGIC_TYPE, FILTER_LOGIC_TYPE, rssiThreshold, rssiThreshold, deliveryMode,
891                    onFoundTimeout, onLostTimeout, onFoundCount, numOfTrackingEntries);
892            gattClientScanFilterParamAddNative(FiltValue);
893        }
894
895        // Get delivery mode based on scan settings.
896        private int getDeliveryMode(ScanClient client) {
897            if (client == null) {
898                return DELIVERY_MODE_IMMEDIATE;
899            }
900            ScanSettings settings = client.settings;
901            if (settings == null) {
902                return DELIVERY_MODE_IMMEDIATE;
903            }
904            if ((settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0
905                    || (settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_MATCH_LOST) != 0) {
906                return DELIVERY_MODE_ON_FOUND_LOST;
907            }
908            return settings.getReportDelayMillis() == 0 ? DELIVERY_MODE_IMMEDIATE
909                    : DELIVERY_MODE_BATCH;
910        }
911
912        private int getScanWindowMillis(ScanSettings settings) {
913            if (settings == null) {
914                return SCAN_MODE_LOW_POWER_WINDOW_MS;
915            }
916            switch (settings.getScanMode()) {
917                case ScanSettings.SCAN_MODE_LOW_LATENCY:
918                    return SCAN_MODE_LOW_LATENCY_WINDOW_MS;
919                case ScanSettings.SCAN_MODE_BALANCED:
920                    return SCAN_MODE_BALANCED_WINDOW_MS;
921                case ScanSettings.SCAN_MODE_LOW_POWER:
922                    return SCAN_MODE_LOW_POWER_WINDOW_MS;
923                default:
924                    return SCAN_MODE_LOW_POWER_WINDOW_MS;
925            }
926        }
927
928        private int getScanIntervalMillis(ScanSettings settings) {
929            if (settings == null)
930                return SCAN_MODE_LOW_POWER_INTERVAL_MS;
931            switch (settings.getScanMode()) {
932                case ScanSettings.SCAN_MODE_LOW_LATENCY:
933                    return SCAN_MODE_LOW_LATENCY_INTERVAL_MS;
934                case ScanSettings.SCAN_MODE_BALANCED:
935                    return SCAN_MODE_BALANCED_INTERVAL_MS;
936                case ScanSettings.SCAN_MODE_LOW_POWER:
937                    return SCAN_MODE_LOW_POWER_INTERVAL_MS;
938                default:
939                    return SCAN_MODE_LOW_POWER_INTERVAL_MS;
940            }
941        }
942
943        private int getOnFoundOnLostTimeoutMillis(ScanSettings settings, boolean onFound) {
944            int factor;
945            int timeout = getScanIntervalMillis(settings);
946
947            // ToDo: This tuning is still work in progress.
948            // How is onfound and onlost timeout computed?
949            // scanning occurs 1 channel per scan interval. Assuming all the
950            // channels are required to be scanned, thats 3 intervals.
951            // For aggressive onfound, we thus have timeout of 3x of
952            // scan interval. At least a factor for 'k' between onfound
953            // and onlost timeout to reduce hysterisis.
954            if (settings.getMatchMode() == ScanSettings.MATCH_MODE_AGGRESSIVE) {
955                factor = MATCH_MODE_AGGRESSIVE_TIMEOUT_FACTOR;
956            } else {
957                factor = MATCH_MODE_STICKY_TIMEOUT_FACTOR;
958            }
959            if (!onFound) factor = factor * ONLOST_FACTOR;
960            return (timeout*factor);
961        }
962
963        private int getOnFoundOnLostSightings(ScanSettings settings) {
964            if (settings == null)
965                return ONFOUND_SIGHTINGS_AGGRESSIVE;
966            if (settings.getMatchMode() == ScanSettings.MATCH_MODE_AGGRESSIVE) {
967                return ONFOUND_SIGHTINGS_AGGRESSIVE;
968            } else {
969                return ONFOUND_SIGHTINGS_STICKY;
970            }
971        }
972
973        private int getNumOfTrackingAdvertisements(ScanSettings settings) {
974            if (settings == null)
975                return 0;
976            int val=0;
977            int maxTotalTrackableAdvertisements =
978                    AdapterService.getAdapterService().getTotalNumOfTrackableAdvertisements();
979            // controller based onfound onlost resources are scarce commodity; the
980            // assignment of filters to num of beacons to track is configurable based
981            // on hw capabilities. Apps give an intent and allocation of onfound
982            // resources or failure there of is done based on availibility - FCFS model
983            switch (settings.getNumOfMatches()) {
984                case ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT:
985                    val = 1;
986                    break;
987                case ScanSettings.MATCH_NUM_FEW_ADVERTISEMENT:
988                    val = 2;
989                    break;
990                case ScanSettings.MATCH_NUM_MAX_ADVERTISEMENT:
991                    val = maxTotalTrackableAdvertisements/2;
992                    break;
993                default:
994                    val = 1;
995                    logd("Invalid setting for getNumOfMatches() " + settings.getNumOfMatches());
996            }
997            return val;
998        }
999
1000        private boolean manageAllocationOfTrackingAdvertisement(int numOfTrackableAdvertisement,
1001                            boolean allocate) {
1002            int maxTotalTrackableAdvertisements =
1003                    AdapterService.getAdapterService().getTotalNumOfTrackableAdvertisements();
1004            synchronized(curUsedTrackableAdvertisements) {
1005                int availableEntries = maxTotalTrackableAdvertisements
1006                                            - curUsedTrackableAdvertisements;
1007                if (allocate) {
1008                    if (availableEntries >= numOfTrackableAdvertisement) {
1009                        curUsedTrackableAdvertisements += numOfTrackableAdvertisement;
1010                        return true;
1011                    } else {
1012                        return false;
1013                    }
1014                } else {
1015                    if (numOfTrackableAdvertisement > curUsedTrackableAdvertisements) {
1016                        return false;
1017                    } else {
1018                         curUsedTrackableAdvertisements -= numOfTrackableAdvertisement;
1019                         return true;
1020                    }
1021                }
1022            }
1023        }
1024
1025
1026        /************************** Regular scan related native methods **************************/
1027        private native void gattClientScanNative(boolean start);
1028
1029        private native void gattSetScanParametersNative(int scan_interval,
1030                int scan_window);
1031
1032        /************************** Filter related native methods ********************************/
1033        private native void gattClientScanFilterAddNative(int client_if,
1034                int filter_type, int filter_index, int company_id,
1035                int company_id_mask, long uuid_lsb, long uuid_msb,
1036                long uuid_mask_lsb, long uuid_mask_msb, String name,
1037                String address, byte addr_type, byte[] data, byte[] mask);
1038
1039        private native void gattClientScanFilterDeleteNative(int client_if,
1040                int filter_type, int filter_index, int company_id,
1041                int company_id_mask, long uuid_lsb, long uuid_msb,
1042                long uuid_mask_lsb, long uuid_mask_msb, String name,
1043                String address, byte addr_type, byte[] data, byte[] mask);
1044
1045        private native void gattClientScanFilterParamAddNative(FilterParams FiltValue);
1046
1047        // Note this effectively remove scan filters for ALL clients.
1048        private native void gattClientScanFilterParamClearAllNative(
1049                int client_if);
1050
1051        private native void gattClientScanFilterParamDeleteNative(
1052                int client_if, int filt_index);
1053
1054        private native void gattClientScanFilterClearNative(int client_if,
1055                int filter_index);
1056
1057        private native void gattClientScanFilterEnableNative(int client_if,
1058                boolean enable);
1059
1060        /************************** Batch related native methods *********************************/
1061        private native void gattClientConfigBatchScanStorageNative(int client_if,
1062                int max_full_reports_percent, int max_truncated_reports_percent,
1063                int notify_threshold_percent);
1064
1065        private native void gattClientStartBatchScanNative(int client_if, int scan_mode,
1066                int scan_interval_unit, int scan_window_unit, int address_type, int discard_rule);
1067
1068        private native void gattClientStopBatchScanNative(int client_if);
1069
1070        private native void gattClientReadScanReportsNative(int client_if, int scan_type);
1071    }
1072
1073    private void logd(String s) {
1074        if (DBG) Log.d(TAG, s);
1075    }
1076}
1077