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