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