Blog

Android Bluetooth Low Energy

In this series of posts I’m describing the implementation of an Android application that scans for BLE devices and displays the result in a list. The code for the application is here. Below is a screenshot of the application after scanning. In this screenshot the application has found three broadcasting BLE devices.

blescan

Blog Post 1: Permissions and Device Support Necessary for BLE Scanning

I’ve done a number of Apps that act a controllers for embedded devices that communicate using Bluetooth Low Energy. Most of the sample code that I have seen, leaves a lot to be desired. So I’ve decided to share some code, as well as, some of the issues I’ve experienced.

I’m assuming that if you are reading this you have some idea of what Bluetooth Low Energy is. Its also sometimes called Bluetooth Smart. But here’s the basic idea: BLE is used by devices that have very little power available to them. Frequently this means they would be powered by so called “button cell” batteries. These devices work by broadcasting BLE packets that advertise their existence and capabilities. Sometimes the broadcast data is all that is available from the device. These devices are usually called Beacons. Sometimes, the broadcast advertises a “connectable” device, wherein another device, like a phone, can make a connection and get additional data.

There are lots of examples of real life devices that make use of the BLE broadcast mechanisms. Some are:

  • Apple iWatch and Android Equivalents
  • Fitness devices like FitBit, or BlastMotion
  • MATTS Martial Arts Total Training Device
  • SBB Smart Back Brace

In this series of blog posts, I’m going to develop a BLE scanner application and explain the principles as I go along.

First, the code discussed in this article is found in GitHub here.

This first post is going to discuss the problem of how to setup an app for BLE scanning. Seems like it should be trivial..

Challenge 1: Permissions

Our first problem when coding and app for Android is to make sure that we have the permissions set correctly. After that we will have to concern ourselves with BLE support.  Its interesting to note that Android has only supported BLE since Android 4.3 Jellybean (API level 18).  So there are still a number of devices in the wild that don’t have BLE support.

Coding for Android permissions got a lot harder as of Android 6.0 Marshmallow.  Prior to 6.0, all one had to do was list the necessary permissions in the manifest. That still works for prior versions. But as of 6.0, permissions have been assigned to one of two categories: Normal or Dangerous. Permissions in the Normal category work that same in 6.0 as they always have. However, permissions in the Dangerous category must be actively agreed to by the user (or not) when the app is run. As it turns out, all the bluetooth specific permissions that we need for this app are considered Normal.  However, in order to receive the results of scans, our app must have at least ACCESS_COARSE_LOCATION. And unfortunately ACCESS_COARSE_LOCATION is categorized as dangerous. Since we are supporting 6.0 we have to deal with this.

The main activity of RanchoBleExample is ScanActivity. (Actually its the only activity.) In it I’ve tried to separate, as much as possible, the housekeeping details of obtaining permissions from the real code. Below is OnCreate() of ScanActivity along with some other details. Notice that we have to have all the permissions granted in order to call the method startTheActivity().

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_scan);
  setupClickableLogo();
  progressBarScanning = (ProgressBar) findViewById(R.id.progressBarScanning);
  scanButton = (Button) findViewById(R.id.scanButton);
  devicesList = (ListView) findViewById(R.id.listView);
  devicesAdapter = new DevicesAdapter(this, deviceScanList);
  devicesList.setAdapter(devicesAdapter);

  // We code to the permissions model of Marshmellos (api >= 23)
  if (allNecessaryPermissionsGranted()) {
    startTheActivity();
  } else {
    requestPermissions();
  }
}

The method allNecessaryPermissionsGranted() is pretty simple, just check for ACCESS_COARSE_LOCATION.

private boolean allNecessaryPermissionsGranted(){
  // on Android 6 (Marshmallow) sdk 23 and above we need Coarse location permission to receive the result of BLE Scans
  int locationPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION);
  return locationPermission == PackageManager.PERMISSION_GRANTED;
}

So if the permissions are Ok, then we just start the activity. Otherwise we have to request permissions then get the activity started if the perms are granted or otherwise do something else if the user decides to deny the request. In our case, if the user denies the request our app isn’t going to be very useful. This request/redo pattern is why I separated out startTheActivity() into its own method so it could be called after the permission acceptance.

Here is the code for requestPermissions() along with some of the other relevant permissions handling code. Notice that there are actually two possible paths to doTheRequest(). This is unfortunately part of the standard Android song-and-dance for requesting permissions. Although this code seems overly complex (and I would agree with that it seems overly complex), its the standard pattern. If any readers have a way to simplify the pattern I’m all ears.

private void requestPermissions() {
  // This code follows the Android patter for requesting permissions
  if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_COARSE_LOCATION)) {
    AlertUtility.showMessageWithOk(this, "Location Permission Required",
            "Bluetooth Low Energy Scanning requires Location permission", null, new DialogInterface.OnClickListener() {
              @Override
              public void onClick(DialogInterface dialog, int which) {
                new Handler().post(new Runnable() {
                  @Override
                  public void run() {
                    doTheRequest();
                  }
                });
              }
            });
  } else {
    doTheRequest();
  }
}

private void doTheRequest(){
  ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
          REQUEST_ACCESS_COARSE_LOCATION);
}

@Override
public void onRequestPermissionsResult(int requestCode,
                                       String permissions[], int[] grantResults) {
  switch (requestCode) {
    case REQUEST_ACCESS_COARSE_LOCATION: {
      // If request is cancelled, the result arrays are empty.
      if (grantResults.length > 0
              && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
         startTheActivity();
      } else {
        // permission denied, boo! Disable the
        // functionality that depends on this permission.
        updateViewFromState();
        AlertUtility.showMessageWithOk(this, "Coarse Location Required", "BLE scans require Coarse Location permission" , null, null);
      }
      return;
    }
  }
}

Challenge 2: Ensuring the device has BLE Support

Now that we have the permissions challenge satisfied, we can move on to Bluetooth. Below is the method startTheActivity(). This is called only once, either from OnCreate() if the permissions were all in order, or from elsewhere after the permissions have been resolved. It has several tasks:

  1. Set up the click listener of the scan button.
  2. Check to see that the device supports BLE and that Bluetooth is On.
  3. Automatically start scanning for BLE devices.

 

private void startTheActivity(){

  scanButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
      startScan();
    }
  });

// we have to make sure ble is supported
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
  AlertUtility.showMessageWithOk(this ,
          getString(R.string.ble_error),
          getString(R.string.no_device_ble_support)
          , null, null);

  disableScan();
} else {
  final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
  bluetoothAdapter = bluetoothManager.getAdapter();

  if (!bluetoothAdapter.isEnabled()){
    Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(intent, REQUEST_ENABLE_BLUETOOTH);
  } else {
    // all is well
    startScan();
  }
}
updateViewFromState();
}

In my next Blog post we’ll get into the actual code for scanning, and even more important, the code for interpreting and understanding the data thats in a scan.

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s