Paul
Paul

Reputation: 21

Running BLE ScanCallback in the background

I want to be able to scan for BLE tags in the background.

I have read about using Services to do this. However, when the service is executed and then I run another application on my phone, the BLE scan callback stops executing but the service still runs as normal.

I know the BLE callback stops because the log cat stops producing the log data until I open the application again.

Below is my MainActiivity:

public class MainActivity extends AppCompatActivity {

BluetoothManager btManager;
BluetoothAdapter btAdapter;
BluetoothLeScanner btScanner;
Button startScanningButton;
Button stopScanningButton;
TextView peripheralTextView;
private NotificationManagerCompat notificationManager;
private final static int REQUEST_ENABLE_BT = 1;
private static final int PERMISSION_REQUEST_COARSE_LOCATION = 1;

private static final String TAG = "ExampleJobService";

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

    Log.d(TAG, "onCreate MAIN");

    notificationManager = NotificationManagerCompat.from(this);
    peripheralTextView = (TextView) findViewById(R.id.PeripheralTextView);
    peripheralTextView.setMovementMethod(new ScrollingMovementMethod());

    startScanningButton = (Button) findViewById(R.id.StartScanButton);

    startScanningButton.setOnClickListener(new View.OnClickListener() {
        public void onClick(View v) {
            Log.d(TAG, "Start button");
            startService(new Intent(MainActivity.this, ExampleService.class));
        }
    });

    stopScanningButton = (Button) findViewById(R.id.StopScanButton);
    stopScanningButton.setOnClickListener(new View.OnClickListener() {
        public void onClick(View v) {
            Log.d(TAG, "Stop 1");
            Intent serviceIntent = new Intent(MainActivity.this, ExampleService.class);
            stopScanning();
            stopService(serviceIntent);
            Log.d(TAG, "Stop 2");

        }
    });

    btManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);
    btAdapter = btManager.getAdapter();
    btScanner = btAdapter.getBluetoothLeScanner();


    if (btAdapter != null && !btAdapter.isEnabled()) {
        Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableIntent,REQUEST_ENABLE_BT);
    }

    // Make sure we have access coarse location enabled, if not, prompt the user to enable it
    if (this.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("This app needs location access");
        builder.setMessage("Please grant location access so this app can detect peripherals.");
        builder.setPositiveButton(android.R.string.ok, null);
        builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
            @Override
            public void onDismiss(DialogInterface dialog) {
                requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSION_REQUEST_COARSE_LOCATION);
            }
        });
        builder.show();
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {

    switch (requestCode) {
        case PERMISSION_REQUEST_COARSE_LOCATION: {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                System.out.println("coarse location permission granted");
            } else {
                final AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setTitle("Functionality limited");
                builder.setMessage("Since location access has not been granted, this app will not be able to discover beacons when in the background.");
                builder.setPositiveButton(android.R.string.ok, null);
                builder.setOnDismissListener(new DialogInterface.OnDismissListener() {

                    @Override
                    public void onDismiss(DialogInterface dialog) {
                    }
                });
                builder.show();
            }
            return;
        }
    }
}

public void startScanning() {

    peripheralTextView.setText("");
    AsyncTask.execute(new Runnable() {
        @Override
        public void run() {
            btScanner.startScan(leScanCallback);
        }
    });
}

public void stopScanning() {
    System.out.println("stopping scanning");
    peripheralTextView.append("Stopped Scanning");
    AsyncTask.execute(new Runnable() {
        @Override
        public void run() {
            btScanner.stopScan(leScanCallback);
        }
    });
}

}

Below is the ExampleService:

public class ExampleService extends Service {

BluetoothManager btManager;
BluetoothAdapter btAdapter;
BluetoothLeScanner btScanner;

TextView peripheralTextView;

private NotificationManagerCompat notificationManager;
private static final String TAG = "ExampleJobService";

@Override
public void onCreate() {
    super.onCreate();
    Log.d(TAG, "onCreate");
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {

    Log.d(TAG, "onStartCommand");

    btManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);
    btAdapter = btManager.getAdapter();
    btScanner = btAdapter.getBluetoothLeScanner();

    //do heavy work on a background thread
    AsyncTask.execute(new Runnable() {
        @Override
        public void run() {
            btScanner.startScan(leScanCallback);
        }
    });

    return START_REDELIVER_INTENT;
}

private ScanCallback leScanCallback = new ScanCallback() {
    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public void onScanResult(int callbackType, ScanResult result) {
        Log.d(TAG, "BLE executed");
        if(result.getDevice().getAddress().equals("EE:7E:DE:9B:65:46") && result.getRssi() > -80){
            Log.d(TAG, "Tag found");
        }
    }
};

@Override
public void onDestroy() {
    Log.d(TAG, "Destroy");
    super.onDestroy();
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return null;
}
}

EDIT 1:

String[] peripheralAddresses = new String[]{"EE:7E:DE:9B:65:46"};

 @Override
public int onStartCommand(Intent intent, int flags, int startId) {

    Log.d(TAG, "onStartCommand");

    Intent notificationIntent = new Intent(this, MainActivity.class);
    PendingIntent pendingIntent = PendingIntent.getActivity(this,
            0, notificationIntent, 0);

    Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("Example Service")
            .setContentText("input")
            .setSmallIcon(R.drawable.ic_one)
            .setContentIntent(pendingIntent)
            .build();
    startForeground(1, notification);

    ScanSettings settings = new ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
            .build();

    // Build filters list
    List<ScanFilter> filters = null;
    if (peripheralAddresses != null) {
        filters = new ArrayList<>();
        for (String address : peripheralAddresses) {
            ScanFilter filter = new ScanFilter.Builder()
                    .setDeviceAddress(address)
                    .build();
            filters.add(filter);
        }
    }

    btScanner.startScan(filters, settings, leScanCallback);

    return START_STICKY;
}

Upvotes: 1

Views: 784

Answers (1)

mrcrambo
mrcrambo

Reputation: 504

First, I recommend you to add Scan filters, because Android stops scan after some time if you scan without them.

Second, Your service will not live for a long time, because Android will kill it, if your device's Android versions is more or equal Android Oreo (Android 8).

So I recommend you to read this article for finding better solution.

Hope, this will help you!

Upvotes: 2

Related Questions