0

I am currently working on an android application, which is connected to a device using BLE. The SDK target version is 23, minVersion is 18. The BLE part was given, and worked until recently, but now the ScanActivity used to detect the device crashes randomly (even before detecting any device).

Here are the logs :

E/AndroidRuntime( 5339): FATAL EXCEPTION: main
E/AndroidRuntime( 5339): Process: xx.xx, PID: 5339
E/AndroidRuntime( 5339): java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.startsWith(java.lang.String)' on a null object reference
E/AndroidRuntime( 5339):    at xx.xx.module.ScanActivity.onScanResult(ScanActivity.java:128)
E/AndroidRuntime( 5339):    at xx.xx.module.bluetooth.BluetoothScanner.onScanResult(BluetoothScanner.java:120)
E/AndroidRuntime( 5339):    at xx.xx.module.bluetooth.scanner.BluetoothLeScannerCompatAPI21ScanCallback.onScanResultBluetoothLeScannerCompat.java:218)
E/AndroidRuntime( 5339):    at android.bluetooth.le.BluetoothLeScanner$BleScanCallbackWrapper$1.run(BluetoothLeScanner.java:330)
E/AndroidRuntime( 5339):    at android.os.Handler.handleCallback(Handler.java:739)
E/AndroidRuntime( 5339):    at android.os.Handler.dispatchMessage(Handler.java:95)
E/AndroidRuntime( 5339):    at android.os.Looper.loop(Looper.java:135)
E/AndroidRuntime( 5339):    at android.app.ActivityThread.main(ActivityThread.java:5319)
E/AndroidRuntime( 5339):    at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime( 5339):    at java.lang.reflect.Method.invoke(Method.java:372)
E/AndroidRuntime( 5339):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1016)
E/AndroidRuntime( 5339):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:811)
W/ActivityManager(  899):   Force finishing activity xx.xx/.module.ScanActivity
I/Process ( 5339): Sending signal. PID: 5339 SIG: 9
I/WindowState(  899): WIN DEATH: Window{4be738c u0 xx.xx/xx.xx.MainActivity}
W/InputDispatcher(  899): channel '103d6676 xx.xx/xx.xx.module.ScanActivity (server)' ~ Consumer closed input channel or an error occurred.  events=0x9
I/ActivityManager(  899): Process xx.xx (pid 5339) has died
E/InputDispatcher(  899): channel '103d6676 xx.xx/xx.xx.module.ScanActivity (server)' ~ Channel is unrecoverably broken and will be disposed!
I/WindowState(  899): WIN DEATH: Window{103d6676 u0 xx.xx/xx.xx.module.ScanActivity}
W/InputDispatcher(  899): Attempted to unregister already unregistered input channel '103d6676 xx.xx/xx.xx.module.ScanActivity (server)'

And the different files involved are below.

What I've tried :

  • Check about permissions (FINE_POSITION, BLUETOOTH, BLE are indeed required).
  • Check about null tests (it seems to me that they are done right)
  • Ask Google (not very helpfull :/)

I'm open to any answer, maybe the code we are using isn't the best ?

ScanActivity :

package xx.xx.module.bluetooth;

import android.app.Activity;
import android.bluetooth.BluetoothDevice;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.Toast;

import xx.xx.R;
import xx.xx.module.bluetooth.scanner.ScannerCallback;
import xx.xx.module.bluetooth.scanner.SnackbarOnBluetoothAdapter;

/**
 * Created by Franck on 08/08/2016.
 */

public class ScanActivity extends Activity implements ScannerCallback {

    private BluetoothListAdapter bluetoothlistAdapter;
    private BluetoothScanner scanner;
    private Button buttonStart, buttonStop;
    private ProgressBar progressBar;
    private ListView listview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.scan_activity_model);

        buttonStart = (Button)findViewById(R.id.buttonStart);
        buttonStop = (Button)findViewById(R.id.buttonStop);
        progressBar = (ProgressBar)findViewById(R.id.progressBar);
        listview = (ListView)findViewById(R.id.listView);

        bluetoothlistAdapter = new BluetoothListAdapter(this.getLayoutInflater());
        listview.setAdapter(bluetoothlistAdapter);

        progressBar.setVisibility( View.GONE );

        buttonStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
//                List<ScanFilterCompat> filters = new ArrayList<ScanFilterCompat>();
//                filters.add( new ScanFilterCompat.Builder().setDeviceName("FPT").build() );
//                filters.add( new ScanFilterCompat.Builder().setDeviceName("FPTLC").build() );
//                ScanSettingsCompat settings = new ScanSettingsCompat.Builder().setScanMode(ScanSettingsCompat.SCAN_MODE_LOW_LATENCY).build();
//                scanner.startLeScan(filters,settings);
                scanner.startLeScan();
            }
        });

        buttonStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                scanner.stopLeScan();
            }
        });

        scanner = new BluetoothScanner(this,this);

        if( !BluetoothScanner.hasBluetooth() ) {
            Toast.makeText(this, R.string.bluetooth_not_supported, Toast.LENGTH_SHORT).show();
            finish();
            return;
        }

        if( !BluetoothScanner.hasBluetoothLE(ScanActivity.this) ) {
            Toast.makeText(this, R.string.bluetooth_low_energy_not_supported, Toast.LENGTH_SHORT).show();
            finish();
            return;
        }
    }

    @Override
    public void onScanState(boolean scanning) {
        if( scanning ) {
            bluetoothlistAdapter.clear();
            progressBar.setVisibility(View.VISIBLE);
        } else {
            progressBar.setVisibility(View.GONE);
        }
    }

    @Override
    public void onScanResult(BluetoothDevice device, int rssi) {
        bluetoothlistAdapter.addDevice(device, rssi);
    }

    @Override
    public void onBluetoothAdapterIsDisable() {
        ViewGroup rootView = (ViewGroup)findViewById(android.R.id.content);
        SnackbarOnBluetoothAdapter.Builder.with(rootView, R.string.bluetooth_disable).withEnableBluetoothButton(R.string.enable).build().showSnackbar();
    }

    @Override
    public void onBluetoothAdapterIsNull() {
        ViewGroup rootView = (ViewGroup)findViewById(android.R.id.content);
        SnackbarOnBluetoothAdapter.Builder.with(rootView, R.string.bluetooth_not_supported).build().showSnackbar();
    }

    @Override
    public void onLocationServiceIsDisable() {
        ViewGroup rootView = (ViewGroup)findViewById(android.R.id.content);
        SnackbarOnBluetoothAdapter.Builder.with(rootView, R.string.location_disable).withEnableLocationButton(R.string.enable).build().showSnackbar();
    }
}

BluetoothScanner :

package xx.xx.module.bluetooth;

import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.os.Build;
import android.os.Handler;
import android.util.Log;

import xx.xx.module.bluetooth.scanner.BluetoothLeScannerCompat;
import xx.xx.module.bluetooth.scanner.ScanCallbackCompat;
import xx.xx.module.bluetooth.scanner.ScanFilterCompat;
import xx.xx.module.bluetooth.scanner.ScanResultCompat;
import xx.xx.module.bluetooth.scanner.ScanSettingsCompat;
import xx.xx.module.bluetooth.scanner.ScannerCallback;

import java.util.List;

/**
 * Created by Franck on 08/08/2016.
 */

public class BluetoothScanner extends ScanCallbackCompat {

    private static final String TAG = "BluetoothScanner";
    public static final long SCAN_PERIOD = 40000;// Stops scanning after 40 seconds.
    private long scan_period = SCAN_PERIOD;
    private ScannerCallback scannerCallback;
    private Handler handler;
    private Context context;

    public BluetoothScanner(Context context, ScannerCallback callback) {
        this.context = context;
        scannerCallback = callback;
        handler = new Handler();
    }

    public void startLeScan() {
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        if( null == adapter ) {
            Log.w(TAG,"Bluetooth adapter is null");
            if( scannerCallback != null ) scannerCallback.onBluetoothAdapterIsNull();
            return;
        }
        if( !adapter.isEnabled() ){
            Log.d(TAG,"Bluetooth adapter is disable");
            if( scannerCallback != null ) scannerCallback.onBluetoothAdapterIsDisable();
            return;
        }

        final int version = Build.VERSION.SDK_INT;
        if (version >= Build.VERSION_CODES.M) {
            LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
            if( !locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) ) {
                Log.d(TAG,"Location service is disable!");
                if( scannerCallback != null ) scannerCallback.onLocationServiceIsDisable();
                return;
            }
        }

        // Stops scanning after a pre-defined scan period.
        handler.postDelayed(new Runnable() {
            @Override
            public void run() { stopLeScan(); }
        }, scan_period);
        // Start scanning
        BluetoothLeScannerCompat.startScan(adapter,this);
        if( scannerCallback != null ) scannerCallback.onScanState(true);
    }

    public void startLeScan(List<ScanFilterCompat> filters, ScanSettingsCompat settings ) {
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        if( null == adapter ) {
            Log.w(TAG,"Bluetooth adapter is null");
            if( scannerCallback != null ) scannerCallback.onBluetoothAdapterIsNull();
            return;
        }
        if( !adapter.isEnabled() ){
            Log.d(TAG,"Bluetooth adapter is disable");
            if( scannerCallback != null ) scannerCallback.onBluetoothAdapterIsDisable();
            return;
        }

        final int version = Build.VERSION.SDK_INT;
        if (version >= Build.VERSION_CODES.M) {
            LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
            if( !locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) ) {
                Log.d(TAG,"Location service is disable!");
                if( scannerCallback != null ) scannerCallback.onLocationServiceIsDisable();
                return;
            }
        }

        // Stops scanning after a pre-defined scan period.
        handler.postDelayed(new Runnable() {
            @Override
            public void run() { stopLeScan(); }
        }, scan_period);
        // Start scanning
        BluetoothLeScannerCompat.startScan(adapter, filters, settings ,this);
        if( scannerCallback != null ) scannerCallback.onScanState(true);
    }

    public void stopLeScan() {
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        if( null == adapter ) {
            Log.w(TAG,"Bluetooth adapter is null");
            if( scannerCallback != null ) scannerCallback.onBluetoothAdapterIsNull();
            return;
        }
        BluetoothLeScannerCompat.stopScan(adapter,this);
        if( scannerCallback != null ) scannerCallback.onScanState(false);
    }

    @Override
    public void onScanResult(int callbackType, ScanResultCompat result) {
        super.onScanResult(callbackType, result);
        if( scannerCallback != null )
            scannerCallback.onScanResult( result.getDevice(), result.getRssi() );
    }

    @Override
    public void onScanFailed(int errorCode) {
        super.onScanFailed(errorCode);
    }

    @Override
    public void onBatchScanResults(List<ScanResultCompat> results) {
        super.onBatchScanResults(results);
    }

    public static boolean hasBluetooth(){
        boolean hasBluetooth = false;
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        if( null != adapter ) {
            hasBluetooth = true;
        }
        return hasBluetooth;
    }

    public static boolean hasBluetoothLE(Context context){
        PackageManager pm = context.getPackageManager();
        boolean hasBLE = pm.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
        return hasBLE;
    }
}

BluetoothLeScannerCompat :

package xx.xx.module.bluetooth.scanner;

import android.annotation.TargetApi;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresPermission;
import android.support.v4.util.SimpleArrayMap;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by Franck on 08/08/2016.
 */

public class BluetoothLeScannerCompat {

    private static final String TAG = "BTLeScannerCompat";

    public static void flushPendingScanResults(@NonNull BluetoothAdapter adapter, @NonNull ScanCallbackCompat callbackCompat) {
        IMPL.flushPendingScanResults(adapter, callbackCompat);
    }

    @RequiresPermission("android.permission.BLUETOOTH_ADMIN")
    public static void startScan(@NonNull BluetoothAdapter adapter, @Nullable List<ScanFilterCompat> filters, @NonNull ScanSettingsCompat settings, @NonNull ScanCallbackCompat callbackCompat) {
        IMPL.startScan(adapter, filters, settings, callbackCompat);
    }

    @RequiresPermission("android.permission.BLUETOOTH_ADMIN")
    public static void startScan(@NonNull BluetoothAdapter adapter, @NonNull ScanCallbackCompat callbackCompat) {
        IMPL.startScan(adapter, callbackCompat);
    }

    @RequiresPermission("android.permission.BLUETOOTH_ADMIN")
    public static void stopScan(@NonNull BluetoothAdapter adapter, @NonNull ScanCallbackCompat callbackCompat) {
        IMPL.stopScan(adapter, callbackCompat);
    }

    static final BluetoothLeScannerCompatImpl IMPL;

    static {
        final int version = Build.VERSION.SDK_INT;
        if (version >= Build.VERSION_CODES.LOLLIPOP) {
            IMPL = new API21BluetoothLeScannerCompatImpl();
        }
        else {
            IMPL = new API18BluetoothLeScannerCompatImpl();
        }
    }

    interface BluetoothLeScannerCompatImpl {
        void flushPendingScanResults(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat);

        void startScan(BluetoothAdapter adapter, List<ScanFilterCompat> filters, ScanSettingsCompat settings, ScanCallbackCompat callbackCompat);

        void startScan(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat);

        void stopScan(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    static class API21BluetoothLeScannerCompatImpl implements BluetoothLeScannerCompatImpl {
        static final SimpleArrayMap<ScanCallbackCompat, API21ScanCallback> callbackMap = new SimpleArrayMap<>();

        @Override
        public void flushPendingScanResults(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat) {
            API21ScanCallback result = callbackMap.get(callbackCompat);
            if (result == null) {
                return;
            }
            adapter.getBluetoothLeScanner().flushPendingScanResults(result);
        }

        @Override
        public void startScan(BluetoothAdapter adapter, List<ScanFilterCompat> filters, ScanSettingsCompat settings, ScanCallbackCompat callbackCompat) {

            List<ScanFilter> scanFilters = null;
            if (filters != null) {
                scanFilters = new ArrayList<>(filters.size());

                for (ScanFilterCompat filter : filters) {
                    scanFilters.add(filter.toApi21());
                }
            }
            if (settings == null) {
                throw new IllegalStateException("Scan settings are null");
            }
            ScanSettings scanSettings = settings.toApi21();
            adapter.getBluetoothLeScanner().startScan(scanFilters, scanSettings, registerCallback(callbackCompat));
        }

        @Override
        public void startScan(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat) {
            adapter.getBluetoothLeScanner().startScan(registerCallback(callbackCompat));
        }

        @Override
        public void stopScan(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat) {
            API21ScanCallback result = callbackMap.remove(callbackCompat);
            if (result == null) {
                return;
            }
            adapter.getBluetoothLeScanner().stopScan(result);
        }

        private API21ScanCallback registerCallback(ScanCallbackCompat callbackCompat) {
            API21ScanCallback result = callbackMap.get(callbackCompat);
            // Attempting to rescan, just let it fail deeper down.
            if (result != null) {
                return result;
            }
            result = new API21ScanCallback(callbackCompat);
            callbackMap.put(callbackCompat, result);
            return result;
        }
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
    static class API18BluetoothLeScannerCompatImpl implements BluetoothLeScannerCompatImpl {
        static final SimpleArrayMap<ScanCallbackCompat, API18ScanCallback> callbackMap = new SimpleArrayMap<>();

        @Override
        public void flushPendingScanResults(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat) {
            // no matching api
        }

        @SuppressWarnings("deprecation")
        @Override
        public void startScan(BluetoothAdapter adapter, List<ScanFilterCompat> filters, ScanSettingsCompat settings, ScanCallbackCompat callbackCompat) {
            adapter.startLeScan(registerCallback(filters, callbackCompat));
        }

        @SuppressWarnings("deprecation")
        @Override
        public void startScan(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat) {
            adapter.startLeScan(registerCallback(null, callbackCompat));
        }

        @SuppressWarnings("deprecation")
        @Override
        public void stopScan(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat) {
            API18ScanCallback callback = callbackMap.remove(callbackCompat);
            if (callback == null) {
                return;
            }
            adapter.stopLeScan(callback);
        }

        private API18ScanCallback registerCallback(List<ScanFilterCompat> filters, ScanCallbackCompat callbackCompat) {
            API18ScanCallback result = callbackMap.get(callbackCompat);
            // Attempting to rescan, just let it fail deeper down.
            if (result != null) {
                return result;
            }
            result = new API18ScanCallback(filters, callbackCompat);
            callbackMap.put(callbackCompat, result);
            return result;
        }
    }

    static class API18ScanCallback implements BluetoothAdapter.LeScanCallback {

        private final List<ScanFilterCompat> filters;
        private final WeakReference<ScanCallbackCompat> callbackCompatRef;

        API18ScanCallback(List<ScanFilterCompat> filters, ScanCallbackCompat callbackCompat) {
            this.filters = filters;
            this.callbackCompatRef = new WeakReference<>(callbackCompat);
        }

        @Override
        public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
            ScanCallbackCompat callbackCompat = callbackCompatRef.get();
            if (callbackCompat == null) return;

            ScanResultCompat result = new ScanResultCompat(
                    device,
                    ScanRecordCompat.parseFromBytes(scanRecord),
                    rssi, System.nanoTime());

            // No filters so return any result
            if (filters == null) {
                callbackCompat.onScanResult(ScanSettingsCompat.CALLBACK_TYPE_ALL_MATCHES, result);
                return;
            }

            // Filters specified, so see if there is a match.
            for (ScanFilterCompat filter : filters) {
                if (filter.matches(result)) {
                    callbackCompat.onScanResult(ScanSettingsCompat.CALLBACK_TYPE_ALL_MATCHES, result);
                    return;
                }
            }
        }
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    static class API21ScanCallback extends ScanCallback {

        private final WeakReference<ScanCallbackCompat> callbackCompatRef;

        API21ScanCallback(ScanCallbackCompat callbackCompat) {
            this.callbackCompatRef = new WeakReference<>(callbackCompat);
        }

        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            ScanCallbackCompat callbackCompat = callbackCompatRef.get();
            if (callbackCompat != null) {
                callbackCompat.onScanResult(callbackType, new ScanResultCompat(result));
            }
        }

        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            ScanCallbackCompat callbackCompat = callbackCompatRef.get();
            if (callbackCompat == null) return;
            List<ScanResultCompat> compatResults = new ArrayList<>(results.size());
            for (ScanResult result : results) {
                compatResults.add(new ScanResultCompat(result));
            }
            callbackCompat.onBatchScanResults(compatResults);
        }

        @Override
        public void onScanFailed(int errorCode) {
            ScanCallbackCompat callbackCompat = callbackCompatRef.get();
            if (callbackCompat != null) {
                callbackCompat.onScanFailed(errorCode);
            }
        }
    }

}
Lou_is
  • 259
  • 3
  • 11
  • 1
    What's the exception? The stacktrace you posted is incomplete. – Andrew Sun Dec 03 '16 at 21:10
  • That's the point, there isn't any other information... I may have missed it, I'll try to reproduce the error and give you an update. – Lou_is Dec 04 '16 at 14:15
  • After managing to reproduce it, I can confirm that there isn't any more information: the scanactivity is just killed right after. – Lou_is Dec 06 '16 at 11:15
  • The stacktrace in your screenshot is definitely missing some lines. Try filtering by app, not by keyword. You may find it easier to just use logcat on your computer. – Andrew Sun Dec 06 '16 at 23:20
  • Thanks, I'm use to look only at the logs I created... I edited my question with the full stacktrace. – Lou_is Dec 07 '16 at 10:11
  • I don't really know the `BluetoothListAdapter` class, but instantiating it with a `LayoutInflater` looks suspicous. Are you sure this line is correct: `bluetoothlistAdapter = new BluetoothListAdapter(this.getLayoutInflater());` – Robin Dec 07 '16 at 10:22
  • @Lou_is You're getting a NPE by calling `startsWith` on a `null` String. (Did you modify the code? I don't see any usages of that in what you posted.) Try stepping through your code with a debugger. http://stackoverflow.com/questions/218384/what-is-a-nullpointerexception-and-how-do-i-fix-it – Andrew Sun Dec 07 '16 at 14:13
  • I didn't change the code, I suppose it occurs in one of the called functions. I'll try to reproduce the error while debugging, and I'll get back to you ;) – Lou_is Dec 07 '16 at 16:53

0 Answers0