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);
}
}
}
}