BLE is wireless technology.It gives context to the environment around you.It perfect for devices that run for long periods on power sources, such as coin cell batteries.

  • low power consumption.
  • small size.
  • connectivity to mobile phones.
  • low cost,robust,efficient.
  • multi-vendor interoperability.
  • global availability, license free.

BLE device remains in sleep mode constantly except for when a connection is initiated. The actual connection times are only a few ms, while Bluetooth takes ~100ms. The reason the connections are short is that the data rates are high at 1 Mb/s.

  • keeping the radio off.
  • Lower standby time, Faster connection, Lower peak power.
  • BLE technology uses only 3 advertising channels.

 

Bluetooth Low Energy example

In the example, the Android app running on an Android device is the GATT client. The app gets data from the GATT server, which is a BLE Battery Level Detector that supports the Battery Level Service.

BLE Permissions


You need BLUETOOTH permission to perform requesting a connection, accepting a connection, and transferring data.You must also declare the BLUETOOTH_ADMIN permission for device discovery or manipulate Bluetooth settings.

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<!--BLE scanning is commonly used to determine a user's
location with Bluetooth LE beacons. -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
   ...
<!-- if your app targets API level 21 or higher. -->
<uses-feature android:name="android.hardware.location.gps" />
 <!--app is available to BLE-capable devices only. -->
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

Android 6.0+ (API level 23+), users grant permissions to apps while the app is running, not when they install the app.

//onResume()
 if (ContextCompat.checkSelfPermission(this.getApplicationContext(),
                android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
 } else {
     ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
                    Constants.REQUEST_LOCATION_ENABLE_CODE);
}

Get the BluetoothAdapter

The BluetoothAdapter represents the device’s Bluetooth adapter.There’s one Bluetooth adapter for the entire system, and your application can interact with it using this object.

private BluetoothAdapter mBluetoothAdapter;
...
//onCreate
BluetoothManager bluetoothManager = (BluetoothManager)
                                    getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();

Enable Bluetooth

If Bluetooth but disabled, then you can request that the user enable Bluetooth without leaving your application.

public static BluetoothAdapter getBluetoothAdapter(Context context) {
      BluetoothManager mBluetoothManager = (BluetoothManager)
      context.getSystemService(context.BLUETOOTH_SERVICE);
      return mBluetoothManager.getAdapter();
}

Scan BLE Devices


To scan BLE devices, you use the startScan() method.Scanning is battery-intensive, you should observe the following guidelines:

  • After finding the desired device, stop scanning.
  • Set a time limit on your scan. A device that was previously available may have moved out of range, and continuing to scan drains the battery.

startScan() method takes a ScanCallback as a parameter. You must implement this callback for results are returned.

private ScanCallback scanCallback = new ScanCallback() {
    @Override
    public void onScanResult(int callbackType, ScanResult result) {
         super.onScanResult(callbackType, result);
         bluetoothDevice = result.getDevice();
         deviceAddress.setText(bluetoothDevice.getAddress());
         deviceName.setText(bluetoothDevice.getName());
         progressBar.setVisibility(View.INVISIBLE);
    }
    @Override
    public void onBatchScanResults(List<ScanResult> results) {
         super.onBatchScanResults(results);
     }
    @Override
    public void onScanFailed(int errorCode) {
         super.onScanFailed(errorCode);
         Log.d(TAG, "Scanning Failed " + errorCode);
         progressBar.setVisibility(View.INVISIBLE);
    }
};

The following snippet shows how to start and stop a scan

private BluetoothLeScanner bluetoothLeScanner;
private BluetoothAdapter mBluetoothAdapter;
private boolean mScanning;
.....
private void startScanning(final boolean enable) {
     bluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
     Handler mHandler = new Handler();
     if (enable) {
        //filter for battery service.
        List<ScanFilter> scanFilters = new ArrayList<>();
        //default setting.
        final ScanSettings settings = new ScanSettings.Builder().build();
        ScanFilter scanFilter = new ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString(SampleGattAttributes.UUID_BATTERY_SERVICE)).build();
        scanFilters.add(scanFilter);
        mHandler.postDelayed(new Runnable() {
          @Override
            public void run() {
                  mScanning = false;
                  progressBar.setVisibility(View.INVISIBLE);
                  bluetoothLeScanner.stopScan(scanCallback);
           }
       }, Constants.SCAN_PERIOD);
       mScanning = true;
       bluetoothLeScanner.startScan(scanFilters, settings, scanCallback);
   } else {
       mScanning = false;
       bluetoothLeScanner.stopScan(scanCallback);
   }
}

In this example, app provides an activity BluetoothDetectorActivity to connect and display data and display GATT services and characteristics supported by the device.

Ble Client
Battery Level Detector

 

Connecting to a GATT Server(BLE Device)


Based on user input, Activity communicates with a Service called BluetoothLEService, which interacts with the BLE device via the Android BLE API

<application>
....
<service android:name=".BluetoothLEService" android:enabled="true" android:exported="true" />
</application>

BluetoothGattCallback: Used to deliver results to the client, such as connection status, as well as any further GATT client operations. 

private BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
         super.onConnectionStateChange(gatt, status, newState);
         Log.d(TAG, "onConnectionStateChange " + newState);
         String intentAction;
         if (newState == BluetoothProfile.STATE_CONNECTED) {
             intentAction = ACTION_GATT_CONNECTED;
             mConnectionState = STATE_CONNECTED;
             broadcastUpdate(intentAction);
             Log.i(TAG, "Connected to GATT server.");
             // Attempts to discover services after successful connection.
             Log.i(TAG, "Attempting to start service discovery:" +
                        mBluetoothGatt.discoverServices());
         } else if (newState == STATE_DISCONNECTED) {
              intentAction = ACTION_GATT_DISCONNECTED;
              mConnectionState = STATE_DISCONNECTED;
              Log.i(TAG, "Disconnected from GATT server.");
              broadcastUpdate(intentAction);
         }
     }
     @Override
     public void onServicesDiscovered(BluetoothGatt gatt, int status) {
          super.onServicesDiscovered(gatt, status);
          Log.d(TAG, "onServicesDiscovered " + status);
          if (status == BluetoothGatt.GATT_SUCCESS) {
             broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
          } else {
             Log.w(TAG, "onServicesDiscovered received: " + status);
          }
        }
        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicRead(gatt, characteristic, status);
            Log.d(TAG, "onCharacteristicRead " + status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
            }
        }
       ....
     @Override
     public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
          super.onCharacteristicChanged(gatt, characteristic);
          Log.d(TAG, "onCharacteristicChanged");
          broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
      }
      ...
    };

The first step in interacting with a BLE device is connecting to the GATT server on the device. To connect to a GATT server on a BLE device, you use the connectGatt() method.

public boolean connect(String address) {
    if (mBluetoothAdapter == null || address == null) {
        Log.w(TAG, "BluetoothAdapter not initialize or unspecified address");
        return false;
    }
    if (mBluetoothAdapter != null && address.equals(bluetoothAddress) && mBluetoothGatt != null) {
        Log.d(TAG, "Try to use existing connection");
        if (mBluetoothGatt.connect()) {
            mConnectionState = STATE_CONNECTING;
            return true;
        } else {
            return false;
        }
    }
    final BluetoothDevice bluetoothDevice = mBluetoothAdapter.getRemoteDevice(address);
    if (bluetoothDevice == null) {
        Log.w(TAG, "Device not found");
        return false;
    }
    mBluetoothGatt = bluetoothDevice.connectGatt(this, false, bluetoothGattCallback);
    bluetoothAddress = address;
    mConnectionState = STATE_CONNECTING;
    return true;
}

When a particular callback is triggered, it calls the broadcastUpdate() helper method and passes it an action. Data parsing in this section is performed in accordance with the Bluetooth Battery Service Measurement profile specifications

private void broadcastUpdate(final String action) {
    final Intent intent = new Intent(action);
    sendBroadcast(intent);
}
private void broadcastUpdate(final String action, final BluetoothGattCharacteristic characteristic) {
    final Intent intent = new Intent(action);
    if (UUID_BATTERY_LEVEL.equals(characteristic.getUuid())) {
        int format = BluetoothGattCharacteristic.FORMAT_UINT8;
        final int battery_level = characteristic.getIntValue(format, 0);
        intent.putExtra(EXTRA_DATA, battery_level+"%");
    }
    sendBroadcast(intent);
}

To enable or disable notifications for a given characteristic setCharacteristicNotification to
BluetoothGatt.Reads the requested characteristic from the associated remote device set
readCharacteristic.

public void readCharacteristic(@NonNull BluetoothGattCharacteristic bluetoothGattCharacteristic) {
      mBluetoothGatt.readCharacteristic(bluetoothGattCharacteristic);
}
public void setCharacteristicNotification(@NonNull BluetoothGattCharacteristic characteristic, boolean enabled) {
        mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
}

In BatteryDetectorActivity, these events are handled by a BroadcastReceiver.

private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
    final String action = intent.getAction();
    if (BluetoothLEService.ACTION_GATT_CONNECTED.equals(action)) {
        mConnected = true;
        updateConnectionState("connected");
    } else if (BluetoothLEService.ACTION_GATT_DISCONNECTED.equals(action)) {
        mConnected = false;
        updateConnectionState("disconnected");
    } else if (BluetoothLEService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
         displayGattServices(mBluetoothLEService.getSupportedGattServices());
    } else if (BluetoothLEService.ACTION_DATA_AVAILABLE.equals(action)) {
         displayData(intent.getStringExtra(BluetoothLEService.EXTRA_DATA));
    }
  }
};

The Following snippet show display Gatt connected service

private void displayGattServices(List<BluetoothGattService> gattServices) {
      if (gattServices == null)
          return;
      String uuid = null;
      String serviceString = "unknown service";
      String charaString = "unknown characteristic";
      for (BluetoothGattService gattService : gattServices) {
           uuid = gattService.getUuid().toString();
           serviceString = SampleGattAttributes.lookup(uuid);
           if (serviceString != null) {
              List<BluetoothGattCharacteristic> gattCharacteristics =
                        gattService.getCharacteristics();
              for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
                   HashMap<String, String> currentCharaData = new HashMap<String, String>();
                   uuid = gattCharacteristic.getUuid().toString();
                   charaString = SampleGattAttributes.lookup(uuid);
                   if (charaString != null) {
                        serviceName.setText(charaString);
                   }
                   mNotifyCharacteristic = gattCharacteristic;
                   return;
               }
            }
        }
    }

Connect Bluetooth service

connectDevice.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View v) {
            if (bluetoothDevice != null) {
                progressBar.setVisibility(View.VISIBLE);
                Intent gattServiceIntent = new Intent(BatteryDetectorActivity.this, BluetoothLEService.class);
                bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
            }
        }
});
//read characteristic notification from server
connectService.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View v) {
            if (mNotifyCharacteristic != null) {
               final int charaProp = mNotifyCharacteristic.getProperties();
               if ((charaProp | BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
                   mBluetoothLEService.readCharacteristic(mNotifyCharacteristic);
               }
               if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
                   mBluetoothLEService.setCharacteristicNotification(mNotifyCharacteristic, true);
               }
            }
        }
    });

Closing the Client App


Once your app has finished using a BLE device, it should call close() so the app can release resources appropriately.

public void close() {
    if (mBluetoothGatt == null) {
        return;
    }
    mBluetoothGatt.close();
    mBluetoothGatt = null;
}

How to test BLE Client?


The BLE Peripheral Simulator is an Android app that allows developers to try out new features of app Bluetooth without the need for a BLE Peripheral Device.
BLE Peripheral with one of three services:

  • Battery Service
  • Heart Rate Service
  • Health Thermometer Service

Use the  Bluetooth features to connect to the app to Read and Write Characteristics, Subscribe to Notifications for when the Characteristics change, and Read and Write Descriptors.
The device in the central role scans, looking for advertisement of Battery Level Detector.

The device in the peripheral role makes the advertisement of Battery Level.

 

Download this project from GitHub.