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