2

I am currently doing a project which requires the android phone app to do background scanning for a particular SensorTag and trigger a notification to remind the user to sanitise their hands.

I have managed to create the app and can receive a notification message when the app is running. My app can scan for ble devices and display them using a listadapter, and can scan in intervals of 10 seconds.

However, I cannot receive any notification message when the phone is sleeping or when I am using another app, which sort of defeat the purpose of my project.

I will appreciate if someone can enlighten me.

Thanks

Chrysline

*P.S. I am using the SensorTag in the broadcaster mode and bluetooth smart mode only. I am targeting API 18 phones only.

public class MainActivity extends ListActivity{

//Bluetooth Management
private BluetoothAdapter btAdapter;
private LeDeviceListAdapter mLeDeviceListAdapter;
private boolean mScanning;

// Set the enable bluetooth code
private final static int REQUEST_ENABLE_BT = 0;


//Handler
private Handler myHandler;
private static final int SCAN_INTERVAL_MS_1 = 10000;

// Notification
private static final int NOTIFY_1 = 0x1001;
private static int mNotificationCount = 1;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    myHandler = new Handler();
    //Check whether BLE is supported on the device(can selectively disable BLE-related features)
    if(!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)){
        Toast.makeText(this, "ble_not_supported", Toast.LENGTH_SHORT).show();
        finish();
    }

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

    //Check if Bluetooth Adapter is present in the device
    if(btAdapter == null)
    {
        Toast.makeText(this, "Error : Bluetooth not supported", Toast.LENGTH_SHORT).show();
        Log.i("DEBUG_TAG", "No bluetooth available");
        finish();
        return;
    }

}

@Override
public void onStart() {
    super.onStart();
}

@Override
public void onResume() {
    super.onResume();

    // check for Bluetooth enabled on each resume
    if (btAdapter != null && !btAdapter.isEnabled()) {
        Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
    }

    //Initialise list view adapter
    mLeDeviceListAdapter = new LeDeviceListAdapter();
    setListAdapter(mLeDeviceListAdapter);
    scanLeDevice(true);
}


@Override
public void onPause() {
    super.onPause();

    mLeDeviceListAdapter.clear();
    scanLeDevice(true);
    Log.i("DEBUG_TOAST", "Scanning in onPause method!!!");

}

@Override
public void onStop() {
    super.onStop();
    scanLeDevice(false);
    mLeDeviceListAdapter.clear();

}

@Override
public void onRestart() {
    super.onRestart();
    scanLeDevice(true);
}

@Override
public void onDestroy() {
    super.onDestroy();
    btAdapter = null;
}

@Override
protected void onActivityResult(int request_enable_bt, int result_enable_bt, Intent data) {
    if (result_enable_bt == RESULT_OK) {
        //Display "Turn On" message
        Toast.makeText(this, "Turned On", Toast.LENGTH_SHORT).show();
        Log.i("DEBUG_TOAST", "Turn On Toast Success!");

        // Display device name and MAC Address
        BluetoothManager btManager =
                (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        btAdapter = btManager.getAdapter();
        String address = btAdapter.getAddress();
        String name = btAdapter.getName();
        String toastText = name + " : " + address;
        Toast.makeText(this, toastText, Toast.LENGTH_LONG).show();

        Log.i("DEBUG_TOAST", "Device Name and Address Toast Success!");
    } else if (result_enable_bt == RESULT_CANCELED) {
        Toast.makeText(this, "Didn't Turn On", Toast.LENGTH_SHORT).show();
        Log.i("DEBUG_TOAST", "Turn Off Toast Success!");

        finish();
    }
    super.onActivityResult(request_enable_bt, result_enable_bt, data);
}

@Override
public boolean onCreateOptionsMenu(Menu menu){
    getMenuInflater().inflate(R.menu.main_menu, menu);
    if(!mScanning){
        menu.findItem(R.id.scan).setVisible(true);
        menu.findItem(R.id.stop).setVisible(false);
        menu.findItem(R.id.exit).setVisible(true);
        menu.findItem(R.id.info).setVisible(true);
        menu.findItem(R.id.refresh).setActionView(null);
        Log.i("DEBUG_TOAST", "Menu_mScanning = 0");
    }

    else{
        menu.findItem(R.id.scan).setVisible(false);
        menu.findItem(R.id.stop).setVisible(true);
        menu.findItem(R.id.exit).setVisible(true);
        menu.findItem(R.id.info).setVisible(true);
        menu.findItem(R.id.refresh).setActionView(
                R.layout.actionbar_indeterminate_progress);
        Log.i("DEBUG_TOAST", "Menu_mScanning = 1");
    }
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item){
    switch (item.getItemId()){
        case R.id.scan:

            if(mLeDeviceListAdapter.getCount() >0)
                mLeDeviceListAdapter.clear();
            scanLeDevice(true);
            Log.i("DEBUG_TOAST", "Scan button pressed!");
            break;

        case R.id.stop:
            scanLeDevice(false);
            break;

        case R.id.info:
            Intent information_Intent = new Intent(MainActivity.this, Information.class);
            startActivity(information_Intent);
            break;

        case R.id.reminder:
            Intent reminder_Intent = new Intent(MainActivity.this, Reminder.class);
            startActivity(reminder_Intent);
            break;

        case  R.id.exit:
            finish();
            break;
    }
    return true;
}

protected void onListItemClick(ListView l, View v, int position, long id) {
    final BluetoothDevice device = mLeDeviceListAdapter.getDevice(position);
    if (device == null) return;
    final Intent intent = new Intent(this, DeviceControlActivity.class);
    intent.putExtra(DeviceControlActivity.EXTRAS_DEVICE_NAME, device.getName());
    intent.putExtra(DeviceControlActivity.EXTRAS_DEVICE_ADDRESS, device.getAddress());
    if (mScanning) {
        btAdapter.stopLeScan(leScanCallback);
        mScanning = false;
    }
    startActivity(intent);
}


private BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(final BluetoothDevice device, final int rssi, byte[] scanRecord) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mLeDeviceListAdapter.addDevice(device, rssi);
                Log.i("DEBUG-ScanCallBack", "add device");
                mLeDeviceListAdapter.notifyDataSetChanged();
                Log.i("DEBUG-ScanCallBack", "notify data set changed");

            }
        });
    }
};


private void scanLeDevice(final boolean enable) {
    if (enable) {
        Log.i("DEBUG_TOAST", "Scanning enabled!");

        // Stops scanning after a pre-defined scan period.
        myHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mScanning = false;
                Log.i("DEBUG_TOAST", "scanLeEnable_mScanning = 0");
                btAdapter.stopLeScan(leScanCallback);
                invalidateOptionsMenu();
            }
        }, SCAN_INTERVAL_MS_1);

        mScanning = true;
        Log.i("DEBUG_TOAST", "scanLeEnable_mScanning = 1");

        btAdapter.startLeScan(leScanCallback);

        new CountDownTimer(5000, 1000) {
            public void onFinish() {
                // When timer is finished
                // Execute your code here

                myHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {

                        mLeDeviceListAdapter.clear();
                        Log.i("DEBUG_TOAST", "List Adapter cleared scan handler");

                        mScanning = true;
                        Log.i("DEBUG_TOAST", "scanLeEnable_mScanning = 1 (Timer)");
                        btAdapter.startLeScan(leScanCallback);
                        invalidateOptionsMenu();
                        scanLeDevice(true);
                    }
                }, SCAN_INTERVAL_MS_1);  }

            public void onTick(long millisUntilFinished) {
                // millisUntilFinished    The amount of time until finished.
            }
        }.start();

    } else {

        mScanning = false;
        Log.i("DEBUG_TOAST", "scanLeNotEnable_mScanning = 0");

        btAdapter.stopLeScan(leScanCallback);
    }
    invalidateOptionsMenu();
}

//Adapter for holding devices found through scanning
private class LeDeviceListAdapter extends BaseAdapter {
    private ArrayList<BluetoothDevice> mLeDevices;
    private LayoutInflater mInflator;
    private final HashMap<BluetoothDevice, Integer> rssiMap = new HashMap<BluetoothDevice, Integer>();

    public LeDeviceListAdapter() {
        super();
        mLeDevices = new ArrayList<BluetoothDevice>();
        Log.i("LISTVIEW_1", "z" );
        mInflator = MainActivity.this.getLayoutInflater();
        Log.i("LISTVIEW_1", "a" );
    }

    public void addDevice(BluetoothDevice device, int rssi) {
        if (!mLeDevices.contains(device)) {
            mLeDevices.add(device);
            Log.i("LISTVIEW_1", "b");
            rssiMap.put(device, rssi);
            Log.i("LISTVIEW_1", "b1");
        }
        else {
            rssiMap.put(device, rssi);
            Log.i("LISTVIEW_1", "b2");
        }
    }

    public BluetoothDevice getDevice(int position) {
        Log.i("LISTVIEW_1", "c");
        return mLeDevices.get(position);
    }

    public void clear() {
        Log.i("LISTVIEW_1", "d" );
        mLeDevices.clear();
    }

    @Override
    public int getCount() {
        Log.i("LISTVIEW_1", "e" + mLeDevices.size());
        return mLeDevices.size();
    }

    @Override
    public Object getItem(int i) {
        Log.i("LISTVIEW_1", "f" );
        return mLeDevices.get(i);
    }

    @Override
    public long getItemId(int i) {
        Log.i("LISTVIEW_1", "g" );
        return i;
    }

    public View getView(int position, View convertView, ViewGroup parent){
        ViewHolder viewHolder;

        if (convertView == null){
            mInflator =
                    (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
            convertView = mInflator.inflate(R.layout.listitem_device, null);
            Log.i("LISTVIEW_1", "0" );
            viewHolder = new ViewHolder();
            viewHolder.deviceAddress = (TextView) convertView.findViewById(R.id.device_address);
            Log.i("LISTVIEW_1", "1" );
            viewHolder.deviceName = (TextView) convertView.findViewById(R.id.device_name);
            Log.i("LISTVIEW_1", "2" );
            viewHolder.deviceRssi = (TextView) convertView.findViewById(R.id.device_rssi);
            Log.i("LISTVIEW_1", "2.5" );
            convertView.setTag(viewHolder);
            Log.i("LISTVIEW_1", "3");
        }
        else{
            viewHolder = (ViewHolder) convertView.getTag();
            Log.i("LISTVIEW_1", "4" );
        }
        BluetoothDevice device = mLeDevices.get(position);
        Log.i("LISTVIEW_1", "5" );
        final String deviceName = device.getName();
        Log.i("LISTVIEW_1", "6");
        final int deviceRSSI = rssiMap.get(device);
        Log.i("LISTVIEW_1", "6.1");

        if(deviceName != null && deviceName.length() > 0 ) {
            if (deviceRSSI >= -50 && deviceName.equals("CC2650 SensorTag") ||
                    deviceName.equals("SimpleBLEBroadcaster")) {

                viewHolder.deviceName.setText(deviceName);

                NotificationManager notifier = (NotificationManager)
                        getSystemService(Context.NOTIFICATION_SERVICE);

                NotificationCompat.Builder notifyBuilder = new
                        NotificationCompat.Builder(getApplicationContext());

                notifyBuilder.setSmallIcon(R.drawable.hand_sanitise_image);
                notifyBuilder.setTicker("Attention!");
                notifyBuilder.setWhen(System.currentTimeMillis());

                Intent toLaunch = new Intent(MainActivity.this,
                        Reminder.class);
                notifyBuilder.setContentIntent(PendingIntent.getActivity(
                        MainActivity.this, 0, toLaunch, 0));
                notifyBuilder.setContentTitle("Take a look!");
                notifyBuilder.setContentText("Will benefit everyone!!! (" + mNotificationCount++
                        + ")");

                notifyBuilder.setAutoCancel(true);
                notifyBuilder.setVibrate(new long[]{0, 200, 200, 600, 600});
                notifier.notify(NOTIFY_1, notifyBuilder.build());

                Log.i("LISTVIEW_1", "Notification Vibrate Success");



                mScanning = false;
                Log.i("DEBUG_TOAST", "scanLeEnable_mScanning = 0 (Notification)");
                btAdapter.stopLeScan(leScanCallback);
                invalidateOptionsMenu();

                new CountDownTimer(10000, 1000) {
                    public void onFinish() {
                        // When timer is finished
                        // Execute your code here

                        mLeDeviceListAdapter.clear();
                        Log.i("LISTVIEW_1", "List Adapter cleared due to notification");
                        scanLeDevice(true);
                        Log.i("LISTVIEW_1", "CountDownTimer Success!");

                    }

                    public void onTick(long millisUntilFinished) {
                        // millisUntilFinished    The amount of time until finished.
                    }
                }.start();
            }


            viewHolder.deviceName.setText(deviceName);
            Log.i("LISTVIEW_1", "8.1");
            viewHolder.deviceAddress.setText(device.getAddress());
            Log.i("LISTVIEW_1", "9.1");
            viewHolder.deviceRssi.setText("" + rssiMap.get(device) + " dBm");
            Log.i("LISTVIEW_1", "10.1");
        }
        else
            viewHolder.deviceName.setText(R.string.unknown_device);
        Log.i("LISTVIEW_1", "8");
        viewHolder.deviceAddress.setText(device.getAddress());
        Log.i("LISTVIEW_1", "9");
        viewHolder.deviceRssi.setText(""+rssiMap.get(device)+" dBm");
        Log.i("LISTVIEW_1", "10");
        return convertView;
    }

}
static class ViewHolder {
    TextView deviceRssi, deviceAddress, deviceName;
}

}

C.Chrysline
  • 35
  • 1
  • 7

1 Answers1

2

If you want to continue scanning for BLE devices when your app is in the background, use a Service. If you don't want the device to sleep, acquire a WakeLock (attention: this drains battery fast!).

TomTasche
  • 5,448
  • 7
  • 41
  • 67
  • I'm really new to Android Programming. May I know how to use Service? I have been trying for the whole day, but to no avail. – C.Chrysline Jan 14 '16 at 06:54
  • There's lots of sample code in the official Android documentation (see link above). Otherwise there's a more complete example in the JavaDoc: http://developer.android.com/reference/android/app/Service.html ("Local Service Sample") – TomTasche Jan 14 '16 at 07:17
  • Do I use my Main Activity as the base activity and add an intent to enter the Service? – C.Chrysline Jan 14 '16 at 08:51
  • Exactly! The Service should then handle all BLE-related tasks. Activity is just meant for UI-purposes. – TomTasche Jan 14 '16 at 08:57
  • May I ask where do I add my scancallback function? Do I add it in Main Activity or the Service file? – C.Chrysline Jan 15 '16 at 06:43
  • Hi, I found a new problem again. Based on the Android monitor, I can see that my device is scanning in the background, and can detect that there are devices in the surrounding. However, it cannot filter the scan results because they cant enter the getView function. If this is the case, how can I do the filter of the scan results and trigger the notification function? – C.Chrysline Jan 15 '16 at 07:46
  • Like I said, all BLE-related tasks happen in the Service. So that's where you register the ScanCallback too. Regarding your second question, I think you should create a new question for that. Please mark my answer as correct so that people know your initial problem was solved. – TomTasche Jan 15 '16 at 10:04
  • This got sensibly easier and more efficient in Oreo https://stackoverflow.com/questions/52777982/scanning-for-ble-devices-on-android-8-in-the-background – mirh Jun 17 '19 at 22:21