1 /*
2  * Copyright (C) 2017 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.googlecode.android_scripting.facade.bluetooth;
18 
19 import android.app.PendingIntent;
20 import android.app.Service;
21 import android.bluetooth.BluetoothAdapter;
22 import android.bluetooth.BluetoothAdapter.LeScanCallback;
23 import android.bluetooth.BluetoothDevice;
24 import android.bluetooth.le.BluetoothLeScanner;
25 import android.bluetooth.le.ScanCallback;
26 import android.bluetooth.le.ScanFilter;
27 import android.bluetooth.le.ScanFilter.Builder;
28 import android.bluetooth.le.ScanResult;
29 import android.bluetooth.le.ScanSettings;
30 import android.content.BroadcastReceiver;
31 import android.content.ComponentName;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.content.IntentFilter;
35 import android.os.Bundle;
36 import android.os.ParcelUuid;
37 
38 import com.googlecode.android_scripting.Log;
39 import com.googlecode.android_scripting.MainThread;
40 import com.googlecode.android_scripting.facade.EventFacade;
41 import com.googlecode.android_scripting.facade.FacadeManager;
42 import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
43 import com.googlecode.android_scripting.rpc.Rpc;
44 import com.googlecode.android_scripting.rpc.RpcOptional;
45 import com.googlecode.android_scripting.rpc.RpcParameter;
46 
47 import java.io.UnsupportedEncodingException;
48 import java.util.ArrayList;
49 import java.util.HashMap;
50 import java.util.List;
51 import java.util.UUID;
52 import java.util.concurrent.Callable;
53 
54 /**
55  * BluetoothLe Scan functions.
56  */
57 
58 public class BluetoothLeScanFacade extends RpcReceiver {
59 
60     private final EventFacade mEventFacade;
61 
62     private BluetoothAdapter mBluetoothAdapter;
63     private static int ScanCallbackCount;
64     private static int FilterListCount;
65     private static int LeScanCallbackCount;
66     private static int ScanSettingsCount;
67     private final Service mService;
68     private final BluetoothLeScanner mScanner;
69     private android.bluetooth.le.ScanSettings.Builder mScanSettingsBuilder;
70     private Builder mScanFilterBuilder;
71     private final HashMap<Integer, myScanCallback> mScanCallbackList;
72     private final HashMap<Integer, myLeScanCallback> mLeScanCallbackList;
73     private final HashMap<Integer, ArrayList<ScanFilter>> mScanFilterList;
74     private final HashMap<Integer, ScanSettings> mScanSettingsList;
75 
BluetoothLeScanFacade(FacadeManager manager)76     public BluetoothLeScanFacade(FacadeManager manager) {
77         super(manager);
78         mService = manager.getService();
79         mBluetoothAdapter = MainThread.run(mService,
80                 new Callable<BluetoothAdapter>() {
81                     @Override
82                     public BluetoothAdapter call() throws Exception {
83                         return BluetoothAdapter.getDefaultAdapter();
84                     }
85                 });
86         mScanner = mBluetoothAdapter.getBluetoothLeScanner();
87         mEventFacade = manager.getReceiver(EventFacade.class);
88         mScanFilterList = new HashMap<Integer, ArrayList<ScanFilter>>();
89         mLeScanCallbackList = new HashMap<Integer, myLeScanCallback>();
90         mScanSettingsList = new HashMap<Integer, ScanSettings>();
91         mScanCallbackList = new HashMap<Integer, myScanCallback>();
92         mScanFilterBuilder = new Builder();
93         mScanSettingsBuilder = new android.bluetooth.le.ScanSettings.Builder();
94     }
95 
96     /**
97      * Constructs a myScanCallback obj and returns its index
98      *
99      * @return Integer myScanCallback.index
100      */
101     @Rpc(description = "Generate a new myScanCallback Object")
bleGenScanCallback()102     public Integer bleGenScanCallback() {
103         ScanCallbackCount += 1;
104         int index = ScanCallbackCount;
105         myScanCallback mScan = new myScanCallback(index);
106         mScanCallbackList.put(mScan.index, mScan);
107         return mScan.index;
108     }
109 
110     /**
111      * Constructs a myLeScanCallback obj and returns its index
112      *
113      * @return Integer myScanCallback.index
114      */
115     @Rpc(description = "Generate a new myScanCallback Object")
bleGenLeScanCallback()116     public Integer bleGenLeScanCallback() {
117         LeScanCallbackCount += 1;
118         int index = LeScanCallbackCount;
119         myLeScanCallback mScan = new myLeScanCallback(index);
120         mLeScanCallbackList.put(mScan.index, mScan);
121         return mScan.index;
122     }
123 
124     /**
125      * Constructs a new filter list array and returns its index
126      *
127      * @return Integer index
128      */
129     @Rpc(description = "Generate a new Filter list")
bleGenFilterList()130     public Integer bleGenFilterList() {
131         FilterListCount += 1;
132         int index = FilterListCount;
133         mScanFilterList.put(index, new ArrayList<ScanFilter>());
134         return index;
135     }
136 
137     /**
138      * Constructs a new filter list array and returns its index
139      *
140      * @return Integer index
141      */
142     @Rpc(description = "Generate a new Filter list")
bleBuildScanFilter( @pcParametername = "filterIndex") Integer filterIndex )143     public Integer bleBuildScanFilter(
144             @RpcParameter(name = "filterIndex")
145             Integer filterIndex
146             ) {
147         mScanFilterList.get(filterIndex).add(mScanFilterBuilder.build());
148         mScanFilterBuilder = new Builder();
149         return mScanFilterList.get(filterIndex).size()-1;
150     }
151 
152     /**
153      * Constructs a new scan setting and returns its index
154      *
155      * @return Integer index
156      */
157     @Rpc(description = "Generate a new scan settings Object")
bleBuildScanSetting()158     public Integer bleBuildScanSetting() {
159         ScanSettingsCount += 1;
160         int index = ScanSettingsCount;
161         mScanSettingsList.put(index, mScanSettingsBuilder.build());
162         mScanSettingsBuilder = new android.bluetooth.le.ScanSettings.Builder();
163         return index;
164     }
165 
166     /**
167      * Stops a ble scan
168      *
169      * @param index the id of the myScan whose ScanCallback to stop
170      * @throws Exception
171      */
172     @Rpc(description = "Stops an ongoing ble advertisement scan")
bleStopBleScan( @pcParametername = "index") Integer index)173     public void bleStopBleScan(
174             @RpcParameter(name = "index")
175             Integer index) throws Exception {
176         Log.d("bluetooth_le_scan mScanCallback " + index);
177         if (mScanCallbackList.get(index) != null) {
178             myScanCallback mScanCallback = mScanCallbackList.get(index);
179             mScanner.stopScan(mScanCallback);
180         } else {
181             throw new Exception("Invalid index input:" + Integer.toString(index));
182         }
183     }
184 
185     /**
186      * Stops a classic ble scan
187      *
188      * @param index the id of the myScan whose LeScanCallback to stop
189      * @throws Exception
190      */
191     @Rpc(description = "Stops an ongoing classic ble scan")
bleStopClassicBleScan( @pcParametername = "index") Integer index)192     public void bleStopClassicBleScan(
193             @RpcParameter(name = "index")
194             Integer index) throws Exception {
195         Log.d("bluetooth_le_scan mLeScanCallback " + index);
196         if (mLeScanCallbackList.get(index) != null) {
197             myLeScanCallback mLeScanCallback = mLeScanCallbackList.get(index);
198             mBluetoothAdapter.stopLeScan(mLeScanCallback);
199         } else {
200             throw new Exception("Invalid index input:" + Integer.toString(index));
201         }
202     }
203 
204     /**
205      * Starts a ble scan with ScanCallback callbacks.
206      *
207      * @param index the id of the myScan whose ScanCallback to start
208      * @throws Exception
209      */
210     @Rpc(description = "Starts a ble advertisement scan using scan callback")
bleStartBleScan( @pcParametername = "filterListIndex") Integer filterListIndex, @RpcParameter(name = "scanSettingsIndex") Integer scanSettingsIndex, @RpcParameter(name = "callbackIndex") Integer callbackIndex )211     public void bleStartBleScan(
212             @RpcParameter(name = "filterListIndex")
213             Integer filterListIndex,
214             @RpcParameter(name = "scanSettingsIndex")
215             Integer scanSettingsIndex,
216             @RpcParameter(name = "callbackIndex")
217             Integer callbackIndex
218             ) throws Exception {
219         Log.d("bluetooth_le_scan starting a background scan");
220         ArrayList<ScanFilter> mScanFilters = new ArrayList<ScanFilter>();
221         mScanFilters.add(new ScanFilter.Builder().build());
222         ScanSettings mScanSettings = new ScanSettings.Builder().build();
223         if (mScanFilterList.get(filterListIndex) != null) {
224             mScanFilters = mScanFilterList.get(filterListIndex);
225         } else {
226             throw new Exception("Invalid filterListIndex input:"
227                     + Integer.toString(filterListIndex));
228         }
229         if (mScanSettingsList.get(scanSettingsIndex) != null) {
230             mScanSettings = mScanSettingsList.get(scanSettingsIndex);
231         } else if (!mScanSettingsList.isEmpty()) {
232             throw new Exception("Invalid scanSettingsIndex input:"
233                     + Integer.toString(scanSettingsIndex));
234         }
235         if (mScanCallbackList.get(callbackIndex) != null) {
236             mScanner.startScan(mScanFilters, mScanSettings,
237                     mScanCallbackList.get(callbackIndex));
238         } else {
239             throw new Exception("Invalid filterListIndex input:"
240                     + Integer.toString(filterListIndex));
241         }
242     }
243 
createPendingIntent()244     private PendingIntent createPendingIntent() {
245         return PendingIntent
246             .getBroadcast(mService, 0,
247                 new Intent(mService, ScanBroadcastReceiver.class)
248                     .setAction("com.googlecode.android_scripting.ACTION_FOUND")
249                     .setComponent(new ComponentName("com.googlecode.android_scripting",
250                         "com.googlecode.android_scripting.facade.bluetooth.ScanBroadcastReceiver")),
251                 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
252     }
253 
254     /**
255      * Starts a ble scan with pending intent callback.
256      *
257      * @param filterListIndex indicate the scan filter list to use.
258      * @param scanSettingsIndex indicate the scan setting to use.
259      */
260     @Rpc(description = "Starts a ble advertisement scan")
bleStartBleScanPendingIntent( @pcParametername = "filterListIndex") Integer filterListIndex, @RpcParameter(name = "scanSettingsIndex") Integer scanSettingsIndex )261     public void bleStartBleScanPendingIntent(
262             @RpcParameter(name = "filterListIndex")
263             Integer filterListIndex,
264             @RpcParameter(name = "scanSettingsIndex")
265             Integer scanSettingsIndex
266     ) throws Exception {
267         Log.d("bluetooth_le_scan starting a background scan using pending intent");
268         ArrayList<ScanFilter> mScanFilters = new ArrayList<ScanFilter>();
269         mScanFilters.add(new ScanFilter.Builder().build());
270         ScanSettings mScanSettings = new ScanSettings.Builder().build();
271         if (mScanFilterList.get(filterListIndex) != null) {
272             mScanFilters = mScanFilterList.get(filterListIndex);
273         } else {
274             throw new IllegalArgumentException("Invalid filterListIndex input:"
275                 + Integer.toString(filterListIndex));
276         }
277         if (mScanSettingsList.get(scanSettingsIndex) != null) {
278             mScanSettings = mScanSettingsList.get(scanSettingsIndex);
279         } else if (!mScanSettingsList.isEmpty()) {
280             throw new IllegalArgumentException("Invalid scanSettingsIndex input:"
281                 + Integer.toString(scanSettingsIndex));
282         }
283         Log.d("Registering receiver");
284         mService.registerReceiver(new TestBroadcastReceiver(),
285                 new IntentFilter(ScanBroadcastReceiver.ACTION_FOUND_SIDESTEP));
286         Log.d("Starting Scan");
287         mScanner.startScan(mScanFilters, mScanSettings, createPendingIntent());
288     }
289 
290     private class TestBroadcastReceiver extends BroadcastReceiver {
291 
292         @Override
onReceive(Context context, Intent intent)293         public void onReceive(Context context, Intent intent) {
294             Log.d("TEST onReceive( " + context + ", " + intent + ")");
295             ArrayList<ScanResult> results = intent
296                     .getParcelableArrayListExtra(BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT);
297             if (results != null && results.size() > 0) {
298                 for (final ScanResult result : results) {
299                     Log.d(
300                             "onScanResult : " + result.getDevice().getName() + " "
301                                 + result.getDevice().getAddress());
302                     new myScanCallback(1)
303                             .onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, result);
304                 }
305             } else {
306                 Log.d("Empty results");
307             }
308         }
309     }
310 
311     /**
312      * Starts a classic ble scan
313      *
314      * @param index the id of the myScan whose ScanCallback to start
315      * @throws Exception
316      */
317     @Rpc(description = "Starts a classic ble advertisement scan")
bleStartClassicBleScan( @pcParametername = "leCallbackIndex") Integer leCallbackIndex )318     public boolean bleStartClassicBleScan(
319             @RpcParameter(name = "leCallbackIndex")
320             Integer leCallbackIndex
321             ) throws Exception {
322         Log.d("bluetooth_le_scan starting a background scan");
323         boolean result = false;
324         if (mLeScanCallbackList.get(leCallbackIndex) != null) {
325             result = mBluetoothAdapter.startLeScan(
326                     mLeScanCallbackList.get(leCallbackIndex));
327         } else {
328             throw new Exception("Invalid leCallbackIndex input:"
329                     + Integer.toString(leCallbackIndex));
330         }
331         return result;
332     }
333 
334     /**
335      * Starts a classic ble scan with service Uuids
336      *
337      * @param index the id of the myScan whose ScanCallback to start
338      * @throws Exception
339      */
340     @Rpc(description = "Starts a classic ble advertisement scan with service Uuids")
bleStartClassicBleScanWithServiceUuids( @pcParametername = "leCallbackIndex") Integer leCallbackIndex, @RpcParameter(name = "serviceUuids") String[] serviceUuidList )341     public boolean bleStartClassicBleScanWithServiceUuids(
342             @RpcParameter(name = "leCallbackIndex")
343             Integer leCallbackIndex,
344             @RpcParameter(name = "serviceUuids")
345             String[] serviceUuidList
346             ) throws Exception {
347         Log.d("bluetooth_le_scan starting a background scan");
348         UUID[] serviceUuids = new UUID[serviceUuidList.length];
349         for (int i = 0; i < serviceUuidList.length; i++) {
350             serviceUuids[i] = UUID.fromString(serviceUuidList[i]);
351         }
352         boolean result = false;
353         if (mLeScanCallbackList.get(leCallbackIndex) != null) {
354             result = mBluetoothAdapter.startLeScan(serviceUuids,
355                     mLeScanCallbackList.get(leCallbackIndex));
356         } else {
357             throw new Exception("Invalid leCallbackIndex input:"
358                     + Integer.toString(leCallbackIndex));
359         }
360         return result;
361     }
362 
363     /**
364      * Trigger onBatchScanResults
365      *
366      * @throws Exception
367      */
368     @Rpc(description = "Gets the results of the ble ScanCallback")
bleFlushPendingScanResults( @pcParametername = "callbackIndex") Integer callbackIndex )369     public void bleFlushPendingScanResults(
370             @RpcParameter(name = "callbackIndex")
371             Integer callbackIndex
372             ) throws Exception {
373         if (mScanCallbackList.get(callbackIndex) != null) {
374             mBluetoothAdapter
375                     .getBluetoothLeScanner().flushPendingScanResults(
376                             mScanCallbackList.get(callbackIndex));
377         } else {
378             throw new Exception("Invalid callbackIndex input:"
379                     + Integer.toString(callbackIndex));
380         }
381     }
382 
383     /**
384      * Set scanSettings for ble scan. Note: You have to set all variables at once.
385      *
386      * @param callbackType Bluetooth LE scan callback type
387      * @param reportDelaySeconds Time of delay for reporting the scan result
388      * @param scanMode Bluetooth LE scan mode.
389      * @param scanResultType Bluetooth LE scan result type
390      * @throws Exception
391      */
392 
393     /**
394      * Set the scan setting's callback type
395      * @param callbackType Bluetooth LE scan callback type
396      */
397     @Rpc(description = "Set the scan setting's callback type")
bleSetScanSettingsCallbackType( @pcParametername = "callbackType") Integer callbackType)398     public void bleSetScanSettingsCallbackType(
399             @RpcParameter(name = "callbackType")
400             Integer callbackType) {
401         mScanSettingsBuilder.setCallbackType(callbackType);
402     }
403 
404     /**
405      * Set the scan setting's report delay millis
406      * @param reportDelayMillis Time of delay for reporting the scan result
407      */
408     @Rpc(description = "Set the scan setting's report delay millis")
bleSetScanSettingsReportDelayMillis( @pcParametername = "reportDelayMillis") Long reportDelayMillis)409     public void bleSetScanSettingsReportDelayMillis(
410             @RpcParameter(name = "reportDelayMillis")
411             Long reportDelayMillis) {
412         mScanSettingsBuilder.setReportDelay(reportDelayMillis);
413     }
414 
415     /**
416      * Set the scan setting's scan mode
417      * @param scanMode Bluetooth LE scan mode.
418      */
419     @Rpc(description = "Set the scan setting's scan mode")
bleSetScanSettingsScanMode( @pcParametername = "scanMode") Integer scanMode)420     public void bleSetScanSettingsScanMode(
421             @RpcParameter(name = "scanMode")
422             Integer scanMode) {
423         mScanSettingsBuilder.setScanMode(scanMode);
424     }
425 
426     /**
427      * Set the scan setting's legacy mode
428      * @param legacy Wether scan is legacy.
429      */
430     @Rpc(description = "Set the scan setting's legacy mode")
bleSetScanSettingsLegacy( @pcParametername = "legacy") Boolean legacy)431     public void bleSetScanSettingsLegacy(
432             @RpcParameter(name = "legacy")
433             Boolean legacy) {
434         mScanSettingsBuilder.setLegacy(legacy);
435     }
436 
437     /**
438      * Set the scan setting's phy mode
439      * @param phy
440      */
441     @Rpc(description = "Set the scan setting's phy mode")
bleSetScanSettingsPhy( @pcParametername = "phy") Integer phy)442     public void bleSetScanSettingsPhy(
443             @RpcParameter(name = "phy")
444             Integer phy) {
445         mScanSettingsBuilder.setPhy(phy);
446     }
447 
448     /**
449      * Set the scan setting's scan result type
450      * @param scanResultType Bluetooth LE scan result type
451      */
452     @Rpc(description = "Set the scan setting's scan result type")
bleSetScanSettingsResultType( @pcParametername = "scanResultType") Integer scanResultType)453     public void bleSetScanSettingsResultType(
454             @RpcParameter(name = "scanResultType")
455             Integer scanResultType) {
456         mScanSettingsBuilder.setScanResultType(scanResultType);
457     }
458     /**
459      * Get ScanSetting's callback type
460      *
461      * @param index the ScanSetting object to use
462      * @return the ScanSetting's callback type
463      * @throws Exception
464      */
465     @Rpc(description = "Get ScanSetting's callback type")
bleGetScanSettingsCallbackType( @pcParametername = "index") Integer index )466     public Integer bleGetScanSettingsCallbackType(
467             @RpcParameter(name = "index")
468             Integer index
469             ) throws Exception {
470         if (mScanSettingsList.get(index) != null) {
471             ScanSettings mScanSettings = mScanSettingsList.get(index);
472             return mScanSettings.getCallbackType();
473         } else {
474             throw new Exception("Invalid index input:" + Integer.toString(index));
475         }
476     }
477 
478     /**
479      * Get ScanSetting's report delay in milli seconds
480      *
481      * @param index the ScanSetting object to useSystemClock
482      * @return the ScanSetting's report delay in milliseconds
483      * @throws Exception
484      */
485     @Rpc(description = "Get ScanSetting's report delay milliseconds")
bleGetScanSettingsReportDelayMillis( @pcParametername = "index") Integer index)486     public Long bleGetScanSettingsReportDelayMillis(
487             @RpcParameter(name = "index")
488             Integer index) throws Exception {
489         if (mScanSettingsList.get(index) != null) {
490             ScanSettings mScanSettings = mScanSettingsList.get(index);
491             return mScanSettings.getReportDelayMillis();
492         } else {
493             throw new Exception("Invalid index input:" + Integer.toString(index));
494         }
495     }
496 
497     /**
498      * Get ScanSetting's scan mode
499      *
500      * @param index the ScanSetting object to use
501      * @return the ScanSetting's scan mode
502      * @throws Exception
503      */
504     @Rpc(description = "Get ScanSetting's scan mode")
bleGetScanSettingsScanMode( @pcParametername = "index") Integer index)505     public Integer bleGetScanSettingsScanMode(
506             @RpcParameter(name = "index")
507             Integer index) throws Exception {
508         if (mScanSettingsList.get(index) != null) {
509             ScanSettings mScanSettings = mScanSettingsList.get(index);
510             return mScanSettings.getScanMode();
511         } else {
512             throw new Exception("Invalid index input:" + Integer.toString(index));
513         }
514     }
515 
516     /**
517      * Get ScanSetting's scan result type
518      *
519      * @param index the ScanSetting object to use
520      * @return the ScanSetting's scan result type
521      * @throws Exception
522      */
523     @Rpc(description = "Get ScanSetting's scan result type")
bleGetScanSettingsScanResultType( @pcParametername = "index") Integer index)524     public Integer bleGetScanSettingsScanResultType(
525             @RpcParameter(name = "index")
526             Integer index) throws Exception {
527         if (mScanSettingsList.get(index) != null) {
528             ScanSettings mScanSettings = mScanSettingsList.get(index);
529             return mScanSettings.getScanResultType();
530         } else {
531             throw new Exception("Invalid index input:"
532                     +    Integer.toString(index));
533         }
534     }
535 
536     /**
537      * Get ScanFilter's Manufacturer Id
538      *
539      * @param index the ScanFilter object to use
540      * @return the ScanFilter's manufacturer id
541      * @throws Exception
542      */
543     @Rpc(description = "Get ScanFilter's Manufacturer Id")
bleGetScanFilterManufacturerId( @pcParametername = "index") Integer index, @RpcParameter(name = "filterIndex") Integer filterIndex)544     public Integer bleGetScanFilterManufacturerId(
545             @RpcParameter(name = "index")
546             Integer index,
547             @RpcParameter(name = "filterIndex")
548             Integer filterIndex)
549             throws Exception {
550         if (mScanFilterList.get(index) != null) {
551             if (mScanFilterList.get(index).get(filterIndex) != null) {
552                 return mScanFilterList.get(index)
553                         .get(filterIndex).getManufacturerId();
554             } else {
555                 throw new Exception("Invalid filterIndex input:"
556                         + Integer.toString(filterIndex));
557             }
558         } else {
559             throw new Exception("Invalid index input:"
560                     + Integer.toString(index));
561         }
562     }
563 
564     /**
565      * Get ScanFilter's device address
566      *
567      * @param index the ScanFilter object to use
568      * @return the ScanFilter's device address
569      * @throws Exception
570      */
571     @Rpc(description = "Get ScanFilter's device address")
bleGetScanFilterDeviceAddress( @pcParametername = "index") Integer index, @RpcParameter(name = "filterIndex") Integer filterIndex)572     public String bleGetScanFilterDeviceAddress(
573             @RpcParameter(name = "index")
574             Integer index,
575             @RpcParameter(name = "filterIndex")
576             Integer filterIndex)
577             throws Exception {
578         if (mScanFilterList.get(index) != null) {
579             if (mScanFilterList.get(index).get(filterIndex) != null) {
580                 return mScanFilterList.get(index).get(
581                         filterIndex).getDeviceAddress();
582             } else {
583                 throw new Exception("Invalid filterIndex input:"
584                         + Integer.toString(filterIndex));
585             }
586         } else {
587             throw new Exception("Invalid index input:"
588                     + Integer.toString(index));
589         }
590     }
591 
592     /**
593      * Get ScanFilter's device address type
594      *
595      * @param index the ScanFilter object to use
596      * @return the ScanFilter's device address type
597      * @throws Exception
598      */
599     @Rpc(description = "Get ScanFilter's device address type")
bleGetScanFilterAddressType( @pcParametername = "index") Integer index, @RpcParameter(name = "filterIndex") Integer filterIndex)600     public Integer bleGetScanFilterAddressType(
601             @RpcParameter(name = "index")
602                     Integer index,
603             @RpcParameter(name = "filterIndex")
604                     Integer filterIndex)
605             throws Exception {
606         if (mScanFilterList.get(index) != null) {
607             if (mScanFilterList.get(index).get(filterIndex) != null) {
608                 return mScanFilterList.get(index).get(
609                         filterIndex).getAddressType();
610             } else {
611                 throw new Exception("Invalid filterIndex input:" + filterIndex);
612             }
613         } else {
614             throw new Exception("Invalid index input:" + index);
615         }
616     }
617 
618     /**
619      * Get ScanFilter's irk
620      *
621      * @param index the ScanFilter object to use
622      * @return the ScanFilter's irk
623      * @throws Exception
624      */
625     @Rpc(description = "Get ScanFilter's irk")
bleGetScanFilterIrk( @pcParametername = "index") Integer index, @RpcParameter(name = "filterIndex") Integer filterIndex)626     public byte[] bleGetScanFilterIrk(
627             @RpcParameter(name = "index")
628                     Integer index,
629             @RpcParameter(name = "filterIndex")
630                     Integer filterIndex)
631             throws Exception {
632         if (mScanFilterList.get(index) != null) {
633             if (mScanFilterList.get(index).get(filterIndex) != null) {
634                 return mScanFilterList.get(index).get(filterIndex).getIrk();
635             } else {
636                 throw new Exception("Invalid filterIndex input:" + filterIndex);
637             }
638         } else {
639             throw new Exception("Invalid index input:" + index);
640         }
641     }
642 
643     /**
644      * Get ScanFilter's device name
645      *
646      * @param index the ScanFilter object to use
647      * @return the ScanFilter's device name
648      * @throws Exception
649      */
650     @Rpc(description = "Get ScanFilter's device name")
bleGetScanFilterDeviceName( @pcParametername = "index") Integer index, @RpcParameter(name = "filterIndex") Integer filterIndex)651     public String bleGetScanFilterDeviceName(
652             @RpcParameter(name = "index")
653             Integer index,
654             @RpcParameter(name = "filterIndex")
655             Integer filterIndex)
656             throws Exception {
657         if (mScanFilterList.get(index) != null) {
658             if (mScanFilterList.get(index).get(filterIndex) != null) {
659                 return mScanFilterList.get(index).get(
660                         filterIndex).getDeviceName();
661             } else {
662                 throw new Exception("Invalid filterIndex input:"
663                         + Integer.toString(filterIndex));
664             }
665         } else {
666             throw new Exception("Invalid index input:"
667                     + Integer.toString(index));
668         }
669     }
670 
671     /**
672      * Get ScanFilter's manufacturer data
673      *
674      * @param index the ScanFilter object to use
675      * @return the ScanFilter's manufacturer data
676      * @throws Exception
677      */
678     @Rpc(description = "Get ScanFilter's manufacturer data")
bleGetScanFilterManufacturerData( @pcParametername = "index") Integer index, @RpcParameter(name = "filterIndex") Integer filterIndex)679     public byte[] bleGetScanFilterManufacturerData(
680             @RpcParameter(name = "index")
681             Integer index,
682             @RpcParameter(name = "filterIndex")
683             Integer filterIndex)
684             throws Exception {
685         if (mScanFilterList.get(index) != null) {
686             if (mScanFilterList.get(index).get(filterIndex) != null) {
687                 return mScanFilterList.get(index).get(
688                         filterIndex).getManufacturerData();
689             } else {
690                 throw new Exception("Invalid filterIndex input:"
691                         + Integer.toString(filterIndex));
692             }
693         } else {
694             throw new Exception("Invalid index input:"
695                     + Integer.toString(index));
696         }
697     }
698 
699     /**
700      * Get ScanFilter's manufacturer data mask
701      *
702      * @param index the ScanFilter object to use
703      * @return the ScanFilter's manufacturer data mask
704      * @throws Exception
705      */
706     @Rpc(description = "Get ScanFilter's manufacturer data mask")
bleGetScanFilterManufacturerDataMask( @pcParametername = "index") Integer index, @RpcParameter(name = "filterIndex") Integer filterIndex)707     public byte[] bleGetScanFilterManufacturerDataMask(
708             @RpcParameter(name = "index")
709             Integer index,
710             @RpcParameter(name = "filterIndex")
711             Integer filterIndex)
712             throws Exception {
713         if (mScanFilterList.get(index) != null) {
714             if (mScanFilterList.get(index).get(filterIndex) != null) {
715                 return mScanFilterList.get(index).get(
716                         filterIndex).getManufacturerDataMask();
717             } else {
718                 throw new Exception("Invalid filterIndex input:"
719                         + Integer.toString(filterIndex));
720             }
721         } else {
722             throw new Exception("Invalid index input:"
723                     + Integer.toString(index));
724         }
725     }
726 
727     /**
728      * Get ScanFilter's service data
729      *
730      * @param index the ScanFilter object to use
731      * @return the ScanFilter's service data
732      * @throws Exception
733      */
734     @Rpc(description = "Get ScanFilter's service data")
bleGetScanFilterServiceData( @pcParametername = "index") Integer index, @RpcParameter(name = "filterIndex") Integer filterIndex)735     public byte[] bleGetScanFilterServiceData(
736             @RpcParameter(name = "index")
737             Integer index,
738             @RpcParameter(name = "filterIndex")
739             Integer filterIndex)
740             throws Exception {
741         if (mScanFilterList.get(index) != null) {
742             if (mScanFilterList.get(index).get(filterIndex) != null) {
743                 return mScanFilterList.get(index).get(
744                         filterIndex).getServiceData();
745             } else {
746                 throw new Exception("Invalid filterIndex input:"
747                         + Integer.toString(filterIndex));
748             }
749         } else {
750             throw new Exception("Invalid index input:"
751                     + Integer.toString(index));
752         }
753     }
754 
755     /**
756      * Get ScanFilter's service data mask
757      *
758      * @param index the ScanFilter object to use
759      * @return the ScanFilter's service data mask
760      * @throws Exception
761      */
762     @Rpc(description = "Get ScanFilter's service data mask")
bleGetScanFilterServiceDataMask( @pcParametername = "index") Integer index, @RpcParameter(name = "filterIndex") Integer filterIndex)763     public byte[] bleGetScanFilterServiceDataMask(
764             @RpcParameter(name = "index")
765             Integer index,
766             @RpcParameter(name = "filterIndex")
767             Integer filterIndex)
768             throws Exception {
769         if (mScanFilterList.get(index) != null) {
770             if (mScanFilterList.get(index).get(filterIndex) != null) {
771                 return mScanFilterList.get(index).get(
772                         filterIndex).getServiceDataMask();
773             } else {
774                 throw new Exception("Invalid filterIndex input:"
775                         + Integer.toString(filterIndex));
776             }
777         } else {
778             throw new Exception("Invalid index input:"
779                     + Integer.toString(index));
780         }
781     }
782 
783     /**
784      * Get ScanFilter's service uuid
785      *
786      * @param index the ScanFilter object to use
787      * @return the ScanFilter's service uuid
788      * @throws Exception
789      */
790     @Rpc(description = "Get ScanFilter's service uuid")
bleGetScanFilterServiceUuid( @pcParametername = "index") Integer index, @RpcParameter(name = "filterIndex") Integer filterIndex)791     public String bleGetScanFilterServiceUuid(
792             @RpcParameter(name = "index")
793             Integer index,
794             @RpcParameter(name = "filterIndex")
795             Integer filterIndex)
796             throws Exception {
797         if (mScanFilterList.get(index) != null) {
798             if (mScanFilterList.get(index).get(filterIndex) != null) {
799                 if (mScanFilterList.get(index).get(
800                         filterIndex).getServiceUuid() != null) {
801                     return mScanFilterList.get(index).get(
802                             filterIndex).getServiceUuid().toString();
803                 } else {
804                     throw new Exception("No Service Uuid set for filter:"
805                             + Integer.toString(filterIndex));
806                 }
807             } else {
808                 throw new Exception("Invalid filterIndex input:"
809                         + Integer.toString(filterIndex));
810             }
811         } else {
812             throw new Exception("Invalid index input:"
813                     + Integer.toString(index));
814         }
815     }
816 
817     /**
818      * Get ScanFilter's service uuid mask
819      *
820      * @param index the ScanFilter object to use
821      * @return the ScanFilter's service uuid mask
822      * @throws Exception
823      */
824     @Rpc(description = "Get ScanFilter's service uuid mask")
bleGetScanFilterServiceUuidMask( @pcParametername = "index") Integer index, @RpcParameter(name = "filterIndex") Integer filterIndex)825     public String bleGetScanFilterServiceUuidMask(
826             @RpcParameter(name = "index")
827             Integer index,
828             @RpcParameter(name = "filterIndex")
829             Integer filterIndex)
830             throws Exception {
831         if (mScanFilterList.get(index) != null) {
832             if (mScanFilterList.get(index).get(filterIndex) != null) {
833                 if (mScanFilterList.get(index).get(
834                         filterIndex).getServiceUuidMask() != null) {
835                     return mScanFilterList.get(
836                             index).get(filterIndex).getServiceUuidMask()
837                             .toString();
838                 } else {
839                     throw new Exception("No Service Uuid Mask set for filter:"
840                             + Integer.toString(filterIndex));
841                 }
842             } else {
843                 throw new Exception("Invalid filterIndex input:"
844                         + Integer.toString(filterIndex));
845             }
846         } else {
847             throw new Exception("Invalid index input:"
848                     + Integer.toString(index));
849         }
850     }
851 
852     /**
853      * Add filter "macAddress" to existing ScanFilter
854      *
855      * @param macAddress the macAddress to filter against
856      * @throws Exception
857      */
858     @Rpc(description = "Add filter \"macAddress\" to existing ScanFilter")
bleSetScanFilterDeviceAddress( @pcParametername = "macAddress") String macAddress )859     public void bleSetScanFilterDeviceAddress(
860             @RpcParameter(name = "macAddress")
861             String macAddress
862             ) {
863             mScanFilterBuilder.setDeviceAddress(macAddress);
864     }
865 
866     /**
867      * Add filter "macAddress", and "addressType" to existing ScanFilter
868      *
869      * @param macAddress the macAddress to filter against
870      * @param addressType the type of macAddress to filter against
871      * @throws Exception
872      */
873     @Rpc(description = "Add filter \"macAddress\" and \"addressType\" to existing ScanFilter")
bleSetScanFilterDeviceAddressAndType( @pcParametername = "macAddress") String macAddress, @RpcParameter(name = "addressType") Integer addressType )874     public void bleSetScanFilterDeviceAddressAndType(
875             @RpcParameter(name = "macAddress") String macAddress,
876             @RpcParameter(name = "addressType") Integer addressType
877     ) {
878         mScanFilterBuilder.setDeviceAddress(macAddress, addressType);
879     }
880 
881     /**
882      * Add filter "macAddress", "addressType", and "irk" to existing ScanFilter
883      *
884      * @param macAddress the macAddress to filter against
885      * @param addressType the type of macAddress to filter against
886      * @param irk IRK for address resolution
887      * @throws Exception
888      */
889     @Rpc(description =
890             "Add filter \"macAddress\", \"addressType\", and \"irk\" to existing ScanFilter")
bleSetScanFilterDeviceAddressTypeAndIrk( @pcParametername = "macAddress") String macAddress, @RpcParameter(name = "addressType") Integer addressType, @RpcParameter(name = "irk") String irk )891     public void bleSetScanFilterDeviceAddressTypeAndIrk(
892             @RpcParameter(name = "macAddress") String macAddress,
893             @RpcParameter(name = "addressType") Integer addressType,
894             @RpcParameter(name = "irk") String irk
895     ) {
896         mScanFilterBuilder.setDeviceAddress(macAddress, addressType, irk.getBytes());
897     }
898 
899     /**
900      * Add filter "macAddress", "addressType", and "irk" to existing ScanFilter
901      *
902      * @param macAddress the macAddress to filter against
903      * @param addressType the type of macAddress to filter against
904      * @param irk IRK for address resolution in hex format
905      * @throws Exception
906      */
907     @Rpc(description =
908             "Add filter \"macAddress\", \"addressType\", and \"irk\" to existing ScanFilter")
bleSetScanFilterDeviceAddressTypeAndIrkHexString( @pcParametername = "macAddress") String macAddress, @RpcParameter(name = "addressType") Integer addressType, @RpcParameter(name = "irk") String irk )909     public void bleSetScanFilterDeviceAddressTypeAndIrkHexString(
910             @RpcParameter(name = "macAddress") String macAddress,
911             @RpcParameter(name = "addressType") Integer addressType,
912             @RpcParameter(name = "irk") String irk
913     ) throws UnsupportedEncodingException {
914         mScanFilterBuilder.setDeviceAddress(macAddress, addressType, hexStringToByteArray(irk));
915     }
916 
hexStringToByteArray(String s)917     private static byte[] hexStringToByteArray(String s) {
918         if (s == null) {
919             throw new IllegalArgumentException("Hex String must not be null");
920         }
921         int len = s.length();
922         if ((len % 2) != 0 || len < 1) { // Multiple of 2 or empty
923             throw new IllegalArgumentException("Hex String must be an even number > 0");
924         }
925         byte[] data = new byte[len / 2];
926         for (int i = 0; i < len; i += 2) {
927             data[i / 2] = (byte) ((byte) (Character.digit(s.charAt(i), 16) << 4)
928                 + (byte) Character.digit(s.charAt(i + 1), 16));
929         }
930         return data;
931     }
932 
933     /**
934      * Add filter "manufacturereDataId and/or manufacturerData" to existing
935      * ScanFilter
936      * @param manufacturerDataId the manufacturer data id to filter against
937      * @param manufacturerDataMask the manufacturere data mask to filter against
938      * @throws Exception
939      */
940     @Rpc(description = "Add filter \"manufacturereDataId and/or manufacturerData\" to existing ScanFilter")
bleSetScanFilterManufacturerData( @pcParametername = "manufacturerDataId") Integer manufacturerDataId, @RpcParameter(name = "manufacturerData") byte[] manufacturerData, @RpcParameter(name = "manufacturerDataMask") @RpcOptional byte[] manufacturerDataMask )941     public void bleSetScanFilterManufacturerData(
942             @RpcParameter(name = "manufacturerDataId")
943             Integer manufacturerDataId,
944             @RpcParameter(name = "manufacturerData")
945             byte[] manufacturerData,
946             @RpcParameter(name = "manufacturerDataMask")
947             @RpcOptional
948             byte[] manufacturerDataMask
949             ){
950         if (manufacturerDataMask != null) {
951             mScanFilterBuilder.setManufacturerData(manufacturerDataId,
952                     manufacturerData, manufacturerDataMask);
953         } else {
954             mScanFilterBuilder.setManufacturerData(manufacturerDataId,
955                     manufacturerData);
956         }
957     }
958 
959     /**
960      * Add filter "serviceData and serviceDataMask" to existing ScanFilter
961      *
962      * @param serviceData the service data to filter against
963      * @param serviceDataMask the servie data mask to filter against
964      * @throws Exception
965      */
966     @Rpc(description = "Add filter \"serviceData and serviceDataMask\" to existing ScanFilter ")
bleSetScanFilterServiceData( @pcParametername = "serviceUuid") String serviceUuid, @RpcParameter(name = "serviceData") byte[] serviceData, @RpcParameter(name = "serviceDataMask") @RpcOptional byte[] serviceDataMask )967     public void bleSetScanFilterServiceData(
968             @RpcParameter(name = "serviceUuid") String serviceUuid,
969             @RpcParameter(name = "serviceData") byte[] serviceData,
970             @RpcParameter(name = "serviceDataMask")
971             @RpcOptional byte[] serviceDataMask
972             ) {
973         if (serviceDataMask != null) {
974             mScanFilterBuilder
975                     .setServiceData(
976                             ParcelUuid.fromString(serviceUuid),
977                             serviceData, serviceDataMask);
978         } else {
979             mScanFilterBuilder.setServiceData(ParcelUuid.fromString(serviceUuid),
980                     serviceData);
981         }
982     }
983 
984     /**
985      * Add filter "serviceUuid and/or serviceMask" to existing ScanFilter
986      *
987      * @param serviceUuid the service uuid to filter against
988      * @param serviceMask the service mask to filter against
989      * @throws Exception
990      */
991     @Rpc(description = "Add filter \"serviceUuid and/or serviceMask\" to existing ScanFilter")
bleSetScanFilterServiceUuid( @pcParametername = "serviceUuid") String serviceUuid, @RpcParameter(name = "serviceMask") @RpcOptional String serviceMask )992     public void bleSetScanFilterServiceUuid(
993             @RpcParameter(name = "serviceUuid")
994             String serviceUuid,
995             @RpcParameter(name = "serviceMask")
996             @RpcOptional
997             String serviceMask
998             ) {
999         if (serviceMask != null) {
1000             mScanFilterBuilder
1001                     .setServiceUuid(ParcelUuid.fromString(serviceUuid),
1002                             ParcelUuid.fromString(serviceMask));
1003         } else {
1004             mScanFilterBuilder.setServiceUuid(ParcelUuid.fromString(serviceUuid));
1005         }
1006     }
1007 
1008     /**
1009      * Add filter "device name" to existing ScanFilter
1010      *
1011      * @param name the device name to filter against
1012      * @throws Exception
1013      */
1014     @Rpc(description = "Sets the scan filter's device name")
bleSetScanFilterDeviceName( @pcParametername = "name") String name )1015     public void bleSetScanFilterDeviceName(
1016             @RpcParameter(name = "name")
1017             String name
1018             ) {
1019             mScanFilterBuilder.setDeviceName(name);
1020     }
1021 
1022     @Rpc(description = "Set the scan setting's match mode")
bleSetScanSettingsMatchMode( @pcParametername = "mode") Integer mode)1023     public void bleSetScanSettingsMatchMode(
1024             @RpcParameter(name = "mode") Integer mode) {
1025         mScanSettingsBuilder.setMatchMode(mode);
1026     }
1027 
1028     @Rpc(description = "Get the scan setting's match mode")
bleGetScanSettingsMatchMode( @pcParametername = "scanSettingsIndex") Integer scanSettingsIndex )1029     public int bleGetScanSettingsMatchMode(
1030             @RpcParameter(name = "scanSettingsIndex") Integer scanSettingsIndex
1031             ) {
1032         return mScanSettingsList.get(scanSettingsIndex).getMatchMode();
1033     }
1034 
1035     @Rpc(description = "Set the scan setting's number of matches")
bleSetScanSettingsNumOfMatches( @pcParametername = "matches") Integer matches)1036     public void bleSetScanSettingsNumOfMatches(
1037             @RpcParameter(name = "matches") Integer matches) {
1038         mScanSettingsBuilder.setNumOfMatches(matches);
1039     }
1040 
1041     @Rpc(description = "Get the scan setting's number of matches")
bleGetScanSettingsNumberOfMatches( @pcParametername = "scanSettingsIndex") Integer scanSettingsIndex)1042     public int bleGetScanSettingsNumberOfMatches(
1043             @RpcParameter(name = "scanSettingsIndex")
1044             Integer scanSettingsIndex) {
1045         return  mScanSettingsList.get(scanSettingsIndex).getNumOfMatches();
1046     }
1047 
1048     private class myScanCallback extends ScanCallback {
1049         public Integer index;
1050         String mEventType;
1051         private final Bundle mResults;
1052 
myScanCallback(Integer idx)1053         public myScanCallback(Integer idx) {
1054             index = idx;
1055             mEventType = "BleScan";
1056             mResults = new Bundle();
1057         }
1058 
1059         @Override
onScanFailed(int errorCode)1060         public void onScanFailed(int errorCode) {
1061             String errorString = "UNKNOWN_ERROR_CODE";
1062             if (errorCode == ScanCallback.SCAN_FAILED_ALREADY_STARTED) {
1063                 errorString = "SCAN_FAILED_ALREADY_STARTED";
1064             } else if (errorCode
1065                     == ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED) {
1066                 errorString = "SCAN_FAILED_APPLICATION_REGISTRATION_FAILED";
1067             } else if (errorCode
1068                     == ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED) {
1069                 errorString = "SCAN_FAILED_FEATURE_UNSUPPORTED";
1070             } else if (errorCode == ScanCallback.SCAN_FAILED_INTERNAL_ERROR) {
1071                 errorString = "SCAN_FAILED_INTERNAL_ERROR";
1072             }
1073             Log.d("bluetooth_le_scan change onScanFailed "
1074                     + mEventType + " " + index + " error "
1075                     + errorString);
1076             mResults.putInt("ID", index);
1077             mResults.putString("Type", "onScanFailed");
1078             mResults.putInt("ErrorCode", errorCode);
1079             mResults.putString("Error", errorString);
1080             mEventFacade.postEvent(mEventType + index + "onScanFailed",
1081                     mResults.clone());
1082             mResults.clear();
1083         }
1084 
1085         @Override
onScanResult(int callbackType, ScanResult result)1086         public void onScanResult(int callbackType, ScanResult result) {
1087             Log.d("bluetooth_le_scan change onUpdate "
1088                     + mEventType + " " + index);
1089             mResults.putInt("ID", index);
1090             mResults.putInt("CallbackType", callbackType);
1091             mResults.putString("Type", "onScanResult");
1092             mResults.putParcelable("Result", result);
1093             mEventFacade.postEvent(
1094                     mEventType + index + "onScanResults", mResults.clone());
1095             mResults.clear();
1096         }
1097 
1098         @Override
onBatchScanResults(List<ScanResult> results)1099         public void onBatchScanResults(List<ScanResult> results) {
1100             Log.d("reportResult " + mEventType + " " + index);
1101             mResults.putLong("Timestamp", System.currentTimeMillis() / 1000);
1102             mResults.putInt("ID", index);
1103             mResults.putString("Type", "onBatchScanResults");
1104             mResults.putParcelableList("Results", results);
1105             mEventFacade.postEvent(mEventType
1106                     + index + "onBatchScanResult", mResults.clone());
1107             mResults.clear();
1108         }
1109     }
1110 
1111     private class myLeScanCallback implements LeScanCallback {
1112         public Integer index;
1113         String mEventType;
1114         private final Bundle mResults;
1115 
myLeScanCallback(Integer idx)1116         public myLeScanCallback(Integer idx) {
1117             index = idx;
1118             mEventType = "ClassicBleScan";
1119             mResults = new Bundle();
1120         }
1121 
1122         @Override
onLeScan( BluetoothDevice device, int rssi, byte[] scanRecord)1123         public void onLeScan(
1124                 BluetoothDevice device, int rssi, byte[] scanRecord) {
1125             Log.d("bluetooth_classic_le_scan " + mEventType + " " + index);
1126             mResults.putParcelable("Device", device);
1127             mResults.putInt("Rssi", rssi);
1128             mResults.putByteArray("ScanRecord", scanRecord);
1129             mResults.putString("Type", "onLeScan");
1130             mEventFacade.postEvent(mEventType
1131                     + index + "onLeScan", mResults.clone());
1132             mResults.clear();
1133         }
1134     }
1135 
1136     @Override
shutdown()1137     public void shutdown() {
1138         if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
1139             for (myScanCallback mScanCallback : mScanCallbackList.values()) {
1140                 if (mScanCallback != null) {
1141                     try {
1142                         mBluetoothAdapter.getBluetoothLeScanner()
1143                                 .stopScan(mScanCallback);
1144                     } catch (NullPointerException e) {
1145                         Log.e("Failed to stop ble scan callback.", e);
1146                     }
1147                 }
1148             }
1149             for (myLeScanCallback mLeScanCallback : mLeScanCallbackList.values()) {
1150                 if (mLeScanCallback != null) {
1151                     try {
1152                         mBluetoothAdapter.stopLeScan(mLeScanCallback);
1153                     } catch (NullPointerException e) {
1154                         Log.e("Failed to stop classic ble scan callback.", e);
1155                     }
1156                 }
1157             }
1158         }
1159         mScanCallbackList.clear();
1160         mScanFilterList.clear();
1161         mScanSettingsList.clear();
1162         mLeScanCallbackList.clear();
1163     }
1164 }
1165