Android BLE- How is onScanResult method being called in ScanCallback?

22,411

Solution 1

I see that you are using the public void startScan (List<ScanFilter> filters, ScanSettings settings, ScanCallback callback) method of startScan(), but you never define any filters. Instead, you pass an empty ArrayList of ScanFilters. So you aren't ever getting any callbacks because you aren't provided any criteria for the filters to match against.

Since you said that you want to scan for all BLE devices, there is no need to use any filters at all. Instead, use the simpler public void startScan (ScanCallback callback) method, which doesn't use any filters or specialized settings.

Regarding your request to understand how it all works - I think you have the concept down based on your code and your expectation that the callbacks should get triggered. You start the scan, and the system goes off and does the scan without blocking your code execution (i.e. it does it asynchronously). While the scan is occurring, it will call one of the three methods in your callback object whenever appropriate (as described in the API documentation). That's pretty much it.

UPDATE: Make sure that you request the BLUETOOTH, BLUETOOTH_ADMIN permissions, as well as the ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permissions. These are required to receive callbacks from the startScan() method. Unfortunately, if you don't request those persmissions, the scan just silently fails. I would prefer if the system either provides a warning message in the logs or triggers a callback to the onScanFailed() method with the errorcode indicating the problem.

Solution 2

So.. I finally found out the answer. For Android Devices that are Android 6.0 or higher (like my phone is Nexus 5x), both GPS and Bluetooth in your phone setting must be turn on, plus in your manifest you must add BLUETOOTH BLUETOOTH_ADMIN ACCESS_FINE_LOCATION ACCESS_COARSE_LOCATION permission.

Now everything work fine for me :)

Share:
22,411
janice chau
Author by

janice chau

Updated on July 09, 2022

Comments

  • janice chau
    janice chau almost 2 years

    This is my first time doing Bluetooth Low Energy in Android project. The project that I am doing is basically to detect all Bluetooth LE devices and connect them to discover their services.

    I would like to ask if anyone know how onScanResult(), onBatchScanResults() and onScanFailed() methods are being called in ScanCallback?

    At First, run scanLeDevice() method.

    BluetoothLeScanner mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
    ScanSettings settings = new ScanSettings.Builder()
               .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
               .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
               .build();
    List<ScanFilter> filters = new ArrayList<ScanFilter>();
    
    scanLeDevice(true);
    

    In this method, it will startScan. So I assume that the scan results are delivered using these callback.

    @TargetApi(21)
    private void scanLeDevice(final boolean enable) {
        if (enable) {
            //stops scanning after a pre-defined scan period
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                        System.out.println("BLE// mLEScanner.stopScan(mScanCallback) ");
                        mLEScanner.stopScan(mScanCallback);
                    }
                }
            }, SCAN_PERIOD);
                System.out.println("BLE// mLEScanner.startScan(filters, settings, mScanCallback)");
                mLEScanner.startScan(filters, settings, mScanCallback);
    
        } else {
                System.out.println("BLE// mLEScanner.stopScan(mScanCallback)");
                mLEScanner.stopScan(mScanCallback);
        }
    }
    

    However, In ScanCallback, I have no idea how it triggers onScanResult and deliver the scan result using the callback. In my testing(as shown below), neither onScanResult() nor onBatchScanResults() and onScanFailed() be called. Can someone explain the concept to me? It will help me a lot!

     /* Scan result for SDK >= 21 */
     private ScanCallback mScanCallback = new ScanCallback() {
    
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            System.out.println("BLE// onScanResult");
            Log.i("callbackType", String.valueOf(callbackType));
            Log.i("result", result.toString());
            BluetoothDevice btDevice = result.getDevice();
            connectToDevice(btDevice);
        }
    
        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            System.out.println("BLE// onBatchScanResults");
            for (ScanResult sr : results) {
                Log.i("ScanResult - Results", sr.toString());
            }
        }
    
        @Override
        public void onScanFailed(int errorCode) {
            System.out.println("BLE// onScanFailed");
            Log.e("Scan Failed", "Error Code: " + errorCode);
        }
    
    };
    

    02-17 10:38:38.513 878-895/? D/BluetoothManagerService: Added callback: android.bluetooth.IBluetoothManagerCallback$Stub$Proxy@8334cf4:true
    02-17 10:38:38.520 782-782/? D/BluetoothAdapter: STATE_ON
    02-17 10:38:38.529 21554-21590/? D/BtGatt.GattService: registerClient() - UUID=835342c6-81eb-4e09-9729-5bbe1c22bc86
    02-17 10:38:38.529 21554-21570/? D/BtGatt.GattService: onClientRegistered() - UUID=835342c6-81eb-4e09-9729-5bbe1c22bc86, clientIf=5
    02-17 10:38:38.530 782-793/? D/BluetoothLeScanner: onClientRegistered() - status=0 clientIf=5
    02-17 10:38:38.530 21554-21599/? D/BtGatt.GattService: start scan with filters
    
    02-17 10:38:38.532 782-782/? I/System.out: BLE// mLEScanner.startScan(filters, settings, mScanCallback)
    02-17 10:38:38.532 21554-21573/? D/BtGatt.ScanManager: handling starting scan
    02-17 10:38:38.534 21576-21577/? I/WCNSS_FILTER: ibs_msm_serial_clock_vote: vote UART CLK ON using UART driver's ioctl()
    02-17 10:38:38.542 21554-21570/? D/BtGatt.GattService: onScanFilterEnableDisabled() - clientIf=5, status=0, action=1
    02-17 10:38:38.543 21554-21570/? D/BtGatt.ScanManager: callback done for clientIf - 5 status - 0
    02-17 10:38:38.543 21554-21573/? D/BtGatt.ScanManager: configureFilterParamter 500 10000 1 0
    02-17 10:38:38.547 21554-21570/? D/BtGatt.GattService: onScanFilterParamsConfigured() - clientIf=5, status=0, action=0, availableSpace=15
    02-17 10:38:38.547 21554-21570/? D/BtGatt.ScanManager: callback done for clientIf - 5 status - 0
    02-17 10:38:38.548 21554-21573/? D/BtGatt.ScanManager: configureRegularScanParams() - queue=1
    02-17 10:38:38.548 487-2827/? I/ACDB-LOADER: ACDB AFE returned = -19
    02-17 10:38:38.549 21554-21573/? D/BtGatt.ScanManager: configureRegularScanParams() - ScanSetting Scan mode=0 mLastConfiguredScanSetting=-2147483648
    02-17 10:38:38.549 21554-21573/? D/BtGatt.ScanManager: configureRegularScanParams - scanInterval = 8000configureRegularScanParams - scanWindow = 800
    02-17 10:38:38.549 21554-21570/? D/BtGatt.GattService: onScanParamSetupCompleted : 0
    02-17 10:38:38.568 21554-21574/? W/bt_hci: filter_incoming_event command complete event with no matching command. opcode: 0x0.
    02-17 10:38:38.603 21554-21570/? D/bt_btif_gattc: btif_gattc_update_properties BLE device name=Polar HR Sensor len=15 dev_type=2
    02-17 10:38:39.571 21576-21585/? I/WCNSS_FILTER: ibs_msm_serial_clock_vote: vote UART CLK OFF using UART driver's ioctl()
    
    02-17 10:38:43.526 782-782/? I/System.out: BLE// mLEScanner.stopScan(mScanCallback) 
    02-17 10:38:43.599 21576-21576/? I/WCNSS_FILTER: ibs_msm_serial_clock_vote: vote UART CLK ON using UART driver's ioctl()
    02-17 10:38:43.967 21576-21576/? I/WCNSS_FILTER: ibs_msm_serial_clock_vote: vote UART CLK OFF using UART driver's ioctl()
    

    Using Android Phone with API 23

    The code I have written here is referred to: http://www.truiton.com/2015/04/android-bluetooth-low-energy-ble-example/



    [Updated Code V1]- Not working

    Here is all my code I've created a virtual Peripheral and it is in advertising mode. The virtual Peripheral is created through an app called LightBlue: https://itunes.apple.com/us/app/lightblue-explorer-bluetooth/id557428110?mt=8 Please help me to check my code :)

    @TargetApi(21)
    public class BluetoothLE extends Fragment {
    
    View view;
    
    private BluetoothAdapter mBluetoothAdapter;
    private int REQUEST_ENABLE_BT = 1;
    private Handler mHandler;
    private static final long SCAN_PERIOD = 5000;  // Stops scanning after 5 seconds
    private BluetoothLeScanner mLEScanner;
    private BluetoothGatt mGatt; //To provide bluetooth communication
    private static final int PERMISSION_REQUEST_COARSE_LOCATION = 1;
    private int permissionCheck;
    
    public BluetoothLE(){
        //empty constructor
    }
    
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
    
        view = inflater.inflate(R.layout.fragment_bluetooth, container, false);
        mHandler = new Handler();
    
        /* check if BLE is supported in this phone */
        if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            Toast.makeText(getActivity(), "BLE Not Supported", Toast.LENGTH_SHORT).show();
            getActivity().finish();
        }
    
        /* Enable bluetooth without leaving app */
        final BluetoothManager bluetoothManager =
                (BluetoothManager) getActivity().getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = bluetoothManager.getAdapter();
    
        /* Build ScanSetting */
        ScanSettings.Builder scanSetting = new ScanSettings.Builder()
                .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
                .setReportDelay(5000);
    
        settings = scanSetting.build();
    
    
        return view;
    }
    
    @TargetApi(Build.VERSION_CODES.M)
    @Override
    public void onResume() {
        super.onResume();
    
        /* Ensures Bluetooth is available on the device and it is enabled. If not, displays a dialog requesting user permission to enable Bluetooth. */
        if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {  //Unable to obtain a BluetoothAdapter
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); //trigger onActivityResult
    
        } else {
            if (Build.VERSION.SDK_INT >= 21) {
                mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
                settings = new ScanSettings.Builder()
                        .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
                        .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
                        .build();
                filters = new ArrayList<ScanFilter>();
            }
    
            if(Build.VERSION.SDK_INT >= 23){
                checkLocationPermission();
            }
    
            scanLeDevice(true);
        }
    }
    
    @Override
    public void onPause() {
        super.onPause();
        if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) {
            scanLeDevice(false);
        }
    }
    
    @Override
    public void onDestroy() {
        if (mGatt == null) {
            return;
        }
        mGatt.close();
        mGatt = null;
        super.onDestroy();
    }
    
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        System.out.println("BLE// onActivityResult");
        if (requestCode == REQUEST_ENABLE_BT) {
            if (resultCode == Activity.RESULT_CANCELED) {
                //Bluetooth not enabled.
                getActivity().finish();
                return;
            }
        }
        super.onActivityResult(requestCode, resultCode, data);
    }
    
    private void scanLeDevice(final boolean enable) {
        if (enable) {
            //stops scanning after a pre-defined scan period
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (Build.VERSION.SDK_INT < 21) {
                        System.out.println("BLE// mBluetoothAdapter.stopLeScan(mLeScanCallback) ");
                        mBluetoothAdapter.stopLeScan(mLeScanCallback);
                    } else {
                        mLEScanner.stopScan(mScanCallback);
                        System.out.println("BLE// mLEScanner.stopScan(mScanCallback) ");
                    }
                }
            }, SCAN_PERIOD);
    
            if (Build.VERSION.SDK_INT < 21) {
                System.out.println("BLE// mBluetoothAdapter.startLeScan(mLeScanCallback)");
                mBluetoothAdapter.startLeScan(mLeScanCallback);
            } else {
    
                mLEScanner.startScan(mScanCallback);
                //mLEScanner.startScan(filters, settings, mScanCallback);
                System.out.println("BLE// mLEScanner.startScan(mScanCallback) ");
            }
        } else {
            if (Build.VERSION.SDK_INT < 21) {
                System.out.println("BLE// mBluetoothAdapter.stopLeScan(mLeScanCallback)");
                mBluetoothAdapter.stopLeScan(mLeScanCallback);
            } else {
                System.out.println("BLE// mLEScanner.stopScan(mScanCallback)");
                mLEScanner.stopScan(mScanCallback);
            }
        }
    }
    
    /* Scan result for SDK >= 21 */
    private ScanCallback mScanCallback = new ScanCallback() {
    
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            System.out.println("BLE// onScanResult");
            super.onScanResult(callbackType, result);
    
            Log.i("callbackType", String.valueOf(callbackType));
            Log.i("result", result.toString());
            Log.i("Device Name: ", result.getDevice().getName());
            System.out.println("Signal: " + result.getRssi());
    
            BluetoothDevice btDevice = result.getDevice();
            connectToDevice(btDevice);
        }
    
        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            System.out.println("BLE// onBatchScanResults");
            for (ScanResult sr : results) {
                Log.i("ScanResult - Results", sr.toString());
            }
        }
    
        @Override
        public void onScanFailed(int errorCode) {
            System.out.println("BLE// onScanFailed");
            Log.e("Scan Failed", "Error Code: " + errorCode);
        }
    
    };
    
    // scan results are returned here SDK < 21
    private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(final BluetoothDevice device, int rssi,
                             byte[] scanRecord) {
            getActivity().runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("BLE// DEVICDE FOUND");
    
                    Log.i("onLeScan", device.toString());
    
                    connectToDevice(device);
                }
            });
        }
    };
    
    public void connectToDevice(BluetoothDevice device) {
        System.out.println("BLE// connectToDevice()");
        if (mGatt == null) {
            mGatt = device.connectGatt(getActivity(), false, gattCallback); //Connect to a GATT Server
            //scanLeDevice(false);// will stop after first device detection
        }
    }
    
    private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            System.out.println("BLE// BluetoothGattCallback");
            Log.i("onConnectionStateChange", "Status: " + status);
            switch (newState) {
                case BluetoothProfile.STATE_CONNECTED:
                    Log.i("gattCallback", "STATE_CONNECTED");
                    gatt.discoverServices();
                    break;
                case BluetoothProfile.STATE_CONNECTING:
                    Log.i("gattCallback", "STATE_CONNECTING");
                    break;
                case BluetoothProfile.STATE_DISCONNECTED:
                    Log.e("gattCallback", "STATE_DISCONNECTED");
                    break;
                default:
                    Log.e("gattCallback", "STATE_OTHER");
            }
        }
    
        @Override
        //New services discovered
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            List<BluetoothGattService> services = gatt.getServices();
            Log.i("onServicesDiscovered", services.toString());
            gatt.readCharacteristic(services.get(1).getCharacteristics().get
                    (0));
        }
    
        @Override
        //Result of a characteristic read operation
        public void onCharacteristicRead(BluetoothGatt gatt,
                                         BluetoothGattCharacteristic
                                                 characteristic, int status) {
            Log.i("onCharacteristicRead", characteristic.toString());
            gatt.disconnect();
        }
    };
    
    public void checkLocationPermission(){
        permissionCheck = ContextCompat.checkSelfPermission(getActivity(), android.Manifest.permission.ACCESS_COARSE_LOCATION);
    
        switch(permissionCheck){
            case PackageManager.PERMISSION_GRANTED:
                break;
    
            case PackageManager.PERMISSION_DENIED:
    
                if(ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), android.Manifest.permission.ACCESS_COARSE_LOCATION)){
                    //Show an explanation to user *asynchronouselly* -- don't block
                    //this thread waiting for the user's response! After user sees the explanation, try again to request the permission
    
                    Snackbar.make(view, "Location access is required to show Bluetooth devices nearby.",
                            Snackbar.LENGTH_LONG).setAction("Action", null).show();
    
                }
                else{
                    //No explanation needed, we can request the permission
                    ActivityCompat.requestPermissions(getActivity(), new String[]{android.Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSION_REQUEST_COARSE_LOCATION);
                }
                break;
        }
    }
    }
    
  • janice chau
    janice chau over 8 years
    Hi rothloup, I've tried the method you said which using startScan(ScanCallback callback). However, non of the three methods in ScanCallback is being called (onScanResult(), onBatchResults() and onScanFailed()). What should I do in order to get my scan result?
  • rothloup
    rothloup over 8 years
    Are you certain that you have BLE devices nearby which are in advertising mode? Please post updated code.
  • janice chau
    janice chau over 8 years
    Hi rothloup, I have post my code in the answer as the comment here has limit characters. Thanks :)
  • rothloup
    rothloup over 8 years
    janice - you can update your original post by editing it. Posting it as an answer will confuse any readers with the same problems as you when they come across this question.
  • rothloup
    rothloup over 8 years
    Have you been able to confirm, using software that is known to work for detecting BLE devices (i.e. Android's built-in bluetooth scan functions), that the device is detectable? I'd hate to chase a SW bug forever just to find out that it's not detecting anything because there is nothing to detect.
  • janice chau
    janice chau over 8 years
    Yep, the virtual peripheral that I've created is detectable in Android's built-in bluetooth scan function.
  • TacoEater
    TacoEater over 7 years
    I had the right permissions but did not prompt the user to enable the location service. Thanks
  • Todd DeLand
    Todd DeLand over 5 years
    It was "Location" for me, in the main phone settings. This was off, even though the app properly requested permissions (and was granted), nothing was found because Location was set to 'Off'. Ugh.
  • Yannick Müller
    Yannick Müller almost 5 years
    Definitely got the "Location" setting problem.