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