ScanManager.java revision e34b91565499994a4b04a1014432c8f90678972a
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.ScanFilter;
23import android.bluetooth.le.ScanSettings;
24import android.content.BroadcastReceiver;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.os.Handler;
29import android.os.HandlerThread;
30import android.os.Looper;
31import android.os.Message;
32import android.os.SystemClock;
33import android.util.Log;
34
35import com.android.bluetooth.Utils;
36import com.android.bluetooth.btservice.AdapterService;
37
38import java.util.ArrayDeque;
39import java.util.ArrayList;
40import java.util.Deque;
41import java.util.HashMap;
42import java.util.HashSet;
43import java.util.List;
44import java.util.Map;
45import java.util.Set;
46import java.util.concurrent.CountDownLatch;
47import java.util.concurrent.TimeUnit;
48
49/**
50 * Class that handles Bluetooth LE scan related operations.
51 *
52 * @hide
53 */
54public class ScanManager {
55    private static final boolean DBG = GattServiceConfig.DBG;
56    private static final String TAG = GattServiceConfig.TAG_PREFIX + "ScanManager";
57
58    // Result type defined in bt stack. Need to be accessed by GattService.
59    static final int SCAN_RESULT_TYPE_TRUNCATED = 1;
60    static final int SCAN_RESULT_TYPE_FULL = 2;
61
62    // Internal messages for handling BLE scan operations.
63    private static final int MSG_START_BLE_SCAN = 0;
64    private static final int MSG_STOP_BLE_SCAN = 1;
65    private static final int MSG_FLUSH_BATCH_RESULTS = 2;
66
67    private static final String ACTION_REFRESH_BATCHED_SCAN =
68            "com.android.bluetooth.gatt.REFRESH_BATCHED_SCAN";
69
70    // Timeout for each controller operation.
71    private static final int OPERATION_TIME_OUT_MILLIS = 500;
72
73    private static int lastConfiguredScanSetting = Integer.MIN_VALUE;
74
75    private GattService mService;
76    private ScanNative mScanNative;
77    private ClientHandler mHandler;
78
79    private Set<ScanClient> mRegularScanClients;
80    private Set<ScanClient> mBatchClients;
81
82    private CountDownLatch mLatch;
83
84    ScanManager(GattService service) {
85        mRegularScanClients = new HashSet<ScanClient>();
86        mBatchClients = new HashSet<ScanClient>();
87        mService = service;
88        mScanNative = new ScanNative();
89    }
90
91    void start() {
92        HandlerThread thread = new HandlerThread("BluetoothScanManager");
93        thread.start();
94        mHandler = new ClientHandler(thread.getLooper());
95    }
96
97    void cleanup() {
98        mRegularScanClients.clear();
99        mBatchClients.clear();
100        mScanNative.cleanup();
101    }
102
103    /**
104     * Returns the combined scan queue of regular scans and batch scans.
105     */
106    List<ScanClient> scanQueue() {
107        List<ScanClient> clients = new ArrayList<>();
108        clients.addAll(mRegularScanClients);
109        clients.addAll(mBatchClients);
110        return clients;
111    }
112
113    void startScan(ScanClient client) {
114        sendMessage(MSG_START_BLE_SCAN, client);
115    }
116
117    void stopScan(ScanClient client) {
118        sendMessage(MSG_STOP_BLE_SCAN, client);
119    }
120
121    void flushBatchScanResults(ScanClient client) {
122        sendMessage(MSG_FLUSH_BATCH_RESULTS, client);
123    }
124
125    void callbackDone(int clientIf, int status) {
126        logd("callback done for clientIf - " + clientIf + " status - " + status);
127        if (status == 0) {
128            mLatch.countDown();
129        }
130        // TODO: add a callback for scan failure.
131    }
132
133    private void sendMessage(int what, ScanClient client) {
134        Message message = new Message();
135        message.what = what;
136        message.obj = client;
137        mHandler.sendMessage(message);
138    }
139
140    private boolean isFilteringSupported() {
141        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
142        return adapter.isOffloadedFilteringSupported();
143    }
144
145    // Handler class that handles BLE scan operations.
146    private class ClientHandler extends Handler {
147
148        ClientHandler(Looper looper) {
149            super(looper);
150        }
151
152        @Override
153        public void handleMessage(Message msg) {
154            ScanClient client = (ScanClient) msg.obj;
155            switch (msg.what) {
156                case MSG_START_BLE_SCAN:
157                    handleStartScan(client);
158                    break;
159                case MSG_STOP_BLE_SCAN:
160                    handleStopScan(client);
161                    break;
162                case MSG_FLUSH_BATCH_RESULTS:
163                    handleFlushBatchResults(client);
164                    break;
165                default:
166                    // Shouldn't happen.
167                    Log.e(TAG, "received an unkown message : " + msg.what);
168            }
169        }
170
171        void handleStartScan(ScanClient client) {
172            Utils.enforceAdminPermission(mService);
173            logd("handling starting scan");
174
175            if (!isScanSupported(client)) {
176                Log.e(TAG, "Scan settings not supported");
177                return;
178            }
179
180            if (mRegularScanClients.contains(client) || mBatchClients.contains(client)) {
181                Log.e(TAG, "Scan already started");
182                return;
183            }
184            // Begin scan operations.
185            if (isBatchClient(client)) {
186                mBatchClients.add(client);
187                mScanNative.startBatchScan(client);
188            } else {
189                mRegularScanClients.add(client);
190                mScanNative.startRegularScan(client);
191                mScanNative.configureRegularScanParams();
192            }
193        }
194
195        void handleStopScan(ScanClient client) {
196            Utils.enforceAdminPermission(mService);
197            if (mRegularScanClients.contains(client)) {
198                mRegularScanClients.remove(client);
199                mScanNative.configureRegularScanParams();
200                mScanNative.stopRegularScan(client);
201            } else {
202                mBatchClients.remove(client);
203                mScanNative.stopBatchScan(client);
204            }
205        }
206
207        void handleFlushBatchResults(ScanClient client) {
208            Utils.enforceAdminPermission(mService);
209            if (!mBatchClients.contains(client)) {
210                return;
211            }
212            mScanNative.flushBatchResults(client.clientIf);
213        }
214
215        private boolean isBatchClient(ScanClient client) {
216            if (client == null || client.settings == null) {
217                return false;
218            }
219            ScanSettings settings = client.settings;
220            return settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ALL_MATCHES &&
221                    settings.getReportDelayMillis() != 0;
222        }
223
224        private boolean isScanSupported(ScanClient client) {
225            if (client == null || client.settings == null) {
226                return true;
227            }
228            ScanSettings settings = client.settings;
229            if (isFilteringSupported()) {
230                return true;
231            }
232            return settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ALL_MATCHES &&
233                    settings.getReportDelayMillis() == 0;
234        }
235    }
236
237    private class ScanNative {
238
239        // Delivery mode defined in bt stack.
240        private static final int DELIVERY_MODE_IMMEDIATE = 0;
241        private static final int DELIVERY_MODE_ON_FOUND = 1;
242        private static final int DELIVERY_MODE_BATCH = 2;
243
244        private static final int ALLOW_ALL_FILTER_INDEX = 1;
245        private static final int ALLOW_ALL_FILTER_SELECTION = 0;
246
247        /**
248         * Scan params corresponding to scan setting
249         */
250        private static final int SCAN_MODE_LOW_POWER_WINDOW_MS = 500;
251        private static final int SCAN_MODE_LOW_POWER_INTERVAL_MS = 5000;
252        private static final int SCAN_MODE_BALANCED_WINDOW_MS = 2000;
253        private static final int SCAN_MODE_BALANCED_INTERVAL_MS = 5000;
254        private static final int SCAN_MODE_LOW_LATENCY_WINDOW_MS = 5000;
255        private static final int SCAN_MODE_LOW_LATENCY_INTERVAL_MS = 5000;
256
257
258        // The logic is AND for each filter field.
259        private static final int LIST_LOGIC_TYPE = 0x1111111;
260        private static final int FILTER_LOGIC_TYPE = 1;
261        // Filter indices that are available to user. It's sad we need to maintain filter index.
262        private final Deque<Integer> mFilterIndexStack;
263        // Map of clientIf and Filter indices used by client.
264        private final Map<Integer, Deque<Integer>> mClientFilterIndexMap;
265        private AlarmManager mAlarmManager;
266        private PendingIntent mBatchScanIntervalIntent;
267
268        ScanNative() {
269            mFilterIndexStack = new ArrayDeque<Integer>();
270            mClientFilterIndexMap = new HashMap<Integer, Deque<Integer>>();
271
272            mAlarmManager = (AlarmManager) mService.getSystemService(Context.ALARM_SERVICE);
273            Intent batchIntent = new Intent(ACTION_REFRESH_BATCHED_SCAN, null);
274            mBatchScanIntervalIntent = PendingIntent.getBroadcast(mService, 0, batchIntent, 0);
275            IntentFilter filter = new IntentFilter();
276            filter.addAction(ACTION_REFRESH_BATCHED_SCAN);
277            mService.registerReceiver(
278                    new BroadcastReceiver() {
279                    @Override
280                        public void onReceive(Context context, Intent intent) {
281                            Log.d(TAG, "awakened up at time " + SystemClock.elapsedRealtime());
282                            String action = intent.getAction();
283
284                            if (action.equals(ACTION_REFRESH_BATCHED_SCAN)) {
285                                if (mBatchClients.isEmpty()) {
286                                    return;
287                                }
288                                // TODO: find out if we need to flush all clients at once.
289                                flushBatchScanResults(mBatchClients.iterator().next());
290                            }
291                        }
292                    }, filter);
293        }
294
295        private void resetCountDownLatch() {
296            mLatch = new CountDownLatch(1);
297        }
298
299        // Returns true if mLatch reaches 0, false if timeout or interrupted.
300        private boolean waitForCallback() {
301            try {
302                return mLatch.await(OPERATION_TIME_OUT_MILLIS, TimeUnit.MILLISECONDS);
303            } catch (InterruptedException e) {
304                return false;
305            }
306        }
307
308        void configureRegularScanParams() {
309            if (DBG) Log.d(TAG, "configureRegularScanParams() - queue=" + mRegularScanClients.size());
310            int curScanSetting = Integer.MIN_VALUE;
311
312            for(ScanClient client : mRegularScanClients) {
313                // ScanClient scan settings are assumed to be monotonically increasing in value for more
314                // power hungry(higher duty cycle) operation
315                if (client.settings.getScanMode() > curScanSetting) {
316                    curScanSetting = client.settings.getScanMode();
317                }
318            }
319
320            if (DBG) Log.d(TAG, "configureRegularScanParams() - ScanSetting Scan mode=" + curScanSetting +
321                    " lastConfiguredScanSetting=" + lastConfiguredScanSetting);
322
323            if (curScanSetting != Integer.MIN_VALUE) {
324                if (curScanSetting != lastConfiguredScanSetting) {
325                    int scanWindow, scanInterval;
326                    switch (curScanSetting){
327                        case ScanSettings.SCAN_MODE_LOW_POWER:
328                            scanWindow = SCAN_MODE_LOW_POWER_WINDOW_MS;
329                            scanInterval = SCAN_MODE_LOW_POWER_INTERVAL_MS;
330                            break;
331                        case ScanSettings.SCAN_MODE_BALANCED:
332                            scanWindow = SCAN_MODE_BALANCED_WINDOW_MS;
333                            scanInterval = SCAN_MODE_BALANCED_INTERVAL_MS;
334                            break;
335                        case ScanSettings.SCAN_MODE_LOW_LATENCY:
336                            scanWindow = SCAN_MODE_LOW_LATENCY_WINDOW_MS;
337                            scanInterval = SCAN_MODE_LOW_LATENCY_INTERVAL_MS;
338                            break;
339                        default:
340                            Log.e(TAG, "Invalid value for curScanSetting " + curScanSetting);
341                            scanWindow = SCAN_MODE_LOW_POWER_WINDOW_MS;
342                            scanInterval = SCAN_MODE_LOW_POWER_INTERVAL_MS;
343                            break;
344                    }
345                    // convert scanWindow and scanInterval from ms to LE scan units(0.625ms)
346                    scanWindow = (scanWindow * 1000)/625;
347                    scanInterval = (scanInterval * 1000)/625;
348                    gattClientScanNative(false);
349                    gattSetScanParametersNative(scanInterval, scanWindow);
350                    gattClientScanNative(true);
351                    lastConfiguredScanSetting = curScanSetting;
352                }
353            } else {
354                lastConfiguredScanSetting = curScanSetting;
355                if (DBG) Log.d(TAG, "configureRegularScanParams() - queue emtpy, scan stopped");
356            }
357        }
358
359
360        void startRegularScan(ScanClient client) {
361            if (mFilterIndexStack.isEmpty() && isFilteringSupported()) {
362                initFilterIndexStack();
363            }
364            if (isFilteringSupported()) {
365                configureScanFilters(client);
366            }
367            // Start scan native only for the first client.
368            if (mRegularScanClients.size() == 1) {
369                gattClientScanNative(true);
370            }
371        }
372
373        void startBatchScan(ScanClient client) {
374            if (mFilterIndexStack.isEmpty() && isFilteringSupported()) {
375                initFilterIndexStack();
376            }
377            configureScanFilters(client);
378            int fullScanPercent = 50;
379            int notifyThreshold = 95;
380            resetCountDownLatch();
381            logd("configuring batch scan storage, appIf " + client.clientIf);
382            gattClientConfigBatchScanStorageNative(client.clientIf, fullScanPercent,
383                    100 - fullScanPercent, notifyThreshold);
384            waitForCallback();
385            int scanMode = getResultType(client.settings);
386            // TODO: configure scan parameters.
387            int scanIntervalUnit = 8;
388            int scanWindowUnit = 8;
389            int discardRule = 2;
390            int addressType = 0;
391            logd("Starting BLE batch scan, scanMode -" + scanMode);
392            gattClientStartBatchScanNative(client.clientIf, scanMode, scanIntervalUnit,
393                    scanWindowUnit, addressType, discardRule);
394            setBatchAlarm();
395        }
396
397        private void setBatchAlarm() {
398            if (mBatchClients.isEmpty()) {
399                mAlarmManager.cancel(mBatchScanIntervalIntent);
400                return;
401            }
402            long batchTriggerIntervalMillis = getBatchTriggerIntervalMillis();
403            mAlarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
404                    SystemClock.elapsedRealtime() + batchTriggerIntervalMillis,
405                    batchTriggerIntervalMillis,
406                    mBatchScanIntervalIntent);
407        }
408
409        void stopRegularScan(ScanClient client) {
410            // Remove scan filters and recycle filter indices.
411            removeScanFilters(client.clientIf);
412            mRegularScanClients.remove(client);
413            if (mRegularScanClients.isEmpty()) {
414                logd("stop scan");
415                gattClientScanNative(false);
416            }
417        }
418
419        void stopBatchScan(ScanClient client) {
420            removeScanFilters(client.clientIf);
421            mBatchClients.remove(client);
422            gattClientStopBatchScanNative(client.clientIf);
423            setBatchAlarm();
424        }
425
426        void flushBatchResults(int clientIf) {
427            logd("flushPendingBatchResults - clientIf = " + clientIf);
428            ScanClient client = getBatchScanClient(clientIf);
429            if (client == null) {
430                logd("unknown client : " + clientIf);
431                return;
432            }
433            int resultType = getResultType(client.settings);
434            gattClientReadScanReportsNative(client.clientIf, resultType);
435        }
436
437        void cleanup() {
438            mAlarmManager.cancel(mBatchScanIntervalIntent);
439        }
440
441        private long getBatchTriggerIntervalMillis() {
442            long intervalMillis = Long.MAX_VALUE;
443            for (ScanClient client : mBatchClients) {
444                if (client.settings != null && client.settings.getReportDelayMillis() > 0) {
445                    intervalMillis = Math.min(intervalMillis,
446                            client.settings.getReportDelayMillis());
447                }
448            }
449            return intervalMillis;
450        }
451
452        // Add scan filters. The logic is:
453        // If no offload filter can/needs to be set, set ALLOW_ALL filter.
454        // Otherwise offload all filters to hardware and enable all filters.
455        private void configureScanFilters(ScanClient client) {
456            int clientIf = client.clientIf;
457            resetCountDownLatch();
458            gattClientScanFilterEnableNative(clientIf, true);
459            waitForCallback();
460
461            if (shouldUseAllowAllFilter(client)) {
462                resetCountDownLatch();
463                configureFilterParamter(clientIf, client, ALLOW_ALL_FILTER_SELECTION,
464                        ALLOW_ALL_FILTER_INDEX);
465                waitForCallback();
466            } else {
467                Deque<Integer> clientFilterIndices = new ArrayDeque<Integer>();
468                for (ScanFilter filter : client.filters) {
469                    ScanFilterQueue queue = new ScanFilterQueue();
470                    queue.addScanFilter(filter);
471                    int featureSelection = queue.getFeatureSelection();
472                    int filterIndex = mFilterIndexStack.pop();
473                    while (!queue.isEmpty()) {
474                        resetCountDownLatch();
475                        addFilterToController(clientIf, queue.pop(), filterIndex);
476                        waitForCallback();
477                    }
478                    resetCountDownLatch();
479                    configureFilterParamter(clientIf, client, featureSelection, filterIndex);
480                    waitForCallback();
481                    clientFilterIndices.add(filterIndex);
482                }
483                mClientFilterIndexMap.put(clientIf, clientFilterIndices);
484            }
485        }
486
487        private void removeScanFilters(int clientIf) {
488            logd("removeScanFilters, clientIf - " + clientIf);
489            Deque<Integer> filterIndices = mClientFilterIndexMap.remove(clientIf);
490            if (filterIndices != null) {
491                mFilterIndexStack.addAll(filterIndices);
492                for (Integer filterIndex : filterIndices) {
493                    resetCountDownLatch();
494                    gattClientScanFilterParamDeleteNative(clientIf, filterIndex);
495                    waitForCallback();
496                }
497            }
498        }
499
500        private ScanClient getBatchScanClient(int clientIf) {
501            for (ScanClient client : mBatchClients) {
502                if (client.clientIf == clientIf) {
503                    return client;
504                }
505            }
506            return null;
507        }
508
509        /**
510         * Return batch scan result type value defined in bt stack.
511         */
512        private int getResultType(ScanSettings settings) {
513            return settings.getScanResultType() == ScanSettings.SCAN_RESULT_TYPE_FULL ?
514                    SCAN_RESULT_TYPE_FULL : SCAN_RESULT_TYPE_TRUNCATED;
515        }
516
517        // Check if ALLOW_FILTER should be used for the client.
518        private boolean shouldUseAllowAllFilter(ScanClient client) {
519            if (client == null) {
520                return true;
521            }
522            if (client.filters == null || client.filters.isEmpty()) {
523                return true;
524            }
525            return client.filters.size() < mClientFilterIndexMap.size();
526        }
527
528        private void addFilterToController(int clientIf, ScanFilterQueue.Entry entry,
529                int filterIndex) {
530            logd("addFilterToController: " + entry.type);
531            switch (entry.type) {
532                case ScanFilterQueue.TYPE_DEVICE_ADDRESS:
533                    logd("add address " + entry.address);
534                    gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0,
535                            0,
536                            "", entry.address, (byte) 0, new byte[0], new byte[0]);
537                    break;
538
539                case ScanFilterQueue.TYPE_SERVICE_DATA:
540                    gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0,
541                            0,
542                            "", "", (byte) 0, entry.data, entry.data_mask);
543                    break;
544
545                case ScanFilterQueue.TYPE_SERVICE_UUID:
546                case ScanFilterQueue.TYPE_SOLICIT_UUID:
547                    gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0,
548                            entry.uuid.getLeastSignificantBits(),
549                            entry.uuid.getMostSignificantBits(),
550                            entry.uuid_mask.getLeastSignificantBits(),
551                            entry.uuid_mask.getMostSignificantBits(),
552                            "", "", (byte) 0, new byte[0], new byte[0]);
553                    break;
554
555                case ScanFilterQueue.TYPE_LOCAL_NAME:
556                    logd("adding filters: " + entry.name);
557                    gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0,
558                            0,
559                            entry.name, "", (byte) 0, new byte[0], new byte[0]);
560                    break;
561
562                case ScanFilterQueue.TYPE_MANUFACTURER_DATA:
563                    int len = entry.data.length;
564                    if (entry.data_mask.length != len)
565                        return;
566                    gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, entry.company,
567                            entry.company_mask, 0, 0, 0, 0, "", "", (byte) 0,
568                            entry.data, entry.data_mask);
569                    break;
570            }
571        }
572
573        private void initFilterIndexStack() {
574            int maxFiltersSupported =
575                    AdapterService.getAdapterService().getNumOfOffloadedScanFilterSupported();
576            // Start from index 2 as index 0 is reserved for ALLOW_ALL filter in Settings app and
577            // index 1 is reserved for ALLOW_ALL filter for regular apps.
578            for (int i = 2; i < maxFiltersSupported; ++i) {
579                mFilterIndexStack.add(i);
580            }
581        }
582
583        // Configure filter parameters.
584        private void configureFilterParamter(int clientIf, ScanClient client, int featureSelection,
585                int filterIndex) {
586            int deliveryMode = getDeliveryMode(client);
587            int rssiThreshold = Byte.MIN_VALUE;
588            gattClientScanFilterParamAddNative(
589                    clientIf, filterIndex, featureSelection, LIST_LOGIC_TYPE,
590                    FILTER_LOGIC_TYPE, rssiThreshold, rssiThreshold, deliveryMode,
591                    0, 0, 0);
592        }
593
594        // Get delivery mode based on scan settings.
595        private int getDeliveryMode(ScanClient client) {
596            if (client == null) {
597                return DELIVERY_MODE_IMMEDIATE;
598            }
599            ScanSettings settings = client.settings;
600            if (settings == null) {
601                return DELIVERY_MODE_IMMEDIATE;
602            }
603            // TODO: double check whether it makes sense to use the same delivery mode for found and
604            // lost.
605            if ((settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0
606                    || (settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_MATCH_LOST) != 0) {
607                return DELIVERY_MODE_ON_FOUND;
608            }
609            return settings.getReportDelayMillis() == 0 ? DELIVERY_MODE_IMMEDIATE
610                    : DELIVERY_MODE_BATCH;
611        }
612
613        /************************** Regular scan related native methods **************************/
614        private native void gattClientScanNative(boolean start);
615
616        private native void gattSetScanParametersNative(int scan_interval,
617                int scan_window);
618
619        /************************** Filter related native methods ********************************/
620        private native void gattClientScanFilterAddNative(int client_if,
621                int filter_type, int filter_index, int company_id,
622                int company_id_mask, long uuid_lsb, long uuid_msb,
623                long uuid_mask_lsb, long uuid_mask_msb, String name,
624                String address, byte addr_type, byte[] data, byte[] mask);
625
626        private native void gattClientScanFilterDeleteNative(int client_if,
627                int filter_type, int filter_index, int company_id,
628                int company_id_mask, long uuid_lsb, long uuid_msb,
629                long uuid_mask_lsb, long uuid_mask_msb, String name,
630                String address, byte addr_type, byte[] data, byte[] mask);
631
632        private native void gattClientScanFilterParamAddNative(
633                int client_if, int filt_index, int feat_seln,
634                int list_logic_type, int filt_logic_type, int rssi_high_thres,
635                int rssi_low_thres, int dely_mode, int found_timeout,
636                int lost_timeout, int found_timeout_cnt);
637
638        // Note this effectively remove scan filters for ALL clients.
639        private native void gattClientScanFilterParamClearAllNative(
640                int client_if);
641
642        private native void gattClientScanFilterParamDeleteNative(
643                int client_if, int filt_index);
644
645        private native void gattClientScanFilterClearNative(int client_if,
646                int filter_index);
647
648        private native void gattClientScanFilterEnableNative(int client_if,
649                boolean enable);
650
651        /************************** Batch related native methods *********************************/
652        private native void gattClientConfigBatchScanStorageNative(int client_if,
653                int max_full_reports_percent, int max_truncated_reports_percent,
654                int notify_threshold_percent);
655
656        private native void gattClientStartBatchScanNative(int client_if, int scan_mode,
657                int scan_interval_unit, int scan_window_unit, int address_type, int discard_rule);
658
659        private native void gattClientStopBatchScanNative(int client_if);
660
661        private native void gattClientReadScanReportsNative(int client_if, int scan_type);
662    }
663
664    private void logd(String s) {
665        Log.d(TAG, s);
666    }
667}
668