Feature-Graphic

How to implement a Beacon scanner?

First of all, do you know what is beacon?

Just like iBeacon is a Bluetooth 4.0 communication protocol designed by Apple, Eddystone is an open Bluetooth 4.0 protocol from Google technology standard, which allows Mobile Apps (running on both iOS and Android devices) to listen for signals from beacons in the physical world and react accordingly. In essence, Eddystone technology allows Mobile Apps to understand their position on a micro-local scale, and deliver hyper-contextual content to users based on location.

Let me show you a simple example – when a customer with an enabled smartphone app is within a 50 meters of the mannequin, the beacon sends a signal providing them with useful information: details about the clothes and accessories the mannequin is wearing, the price, where the items can be found within the store and links to purchase the items directly from the retailer’s website.

Here comes the interesting part. How to implement a beacon scanner in order to detect these devices? I’m not going to waste your time anymore. Let’s begin!

1) Import altbeacon library to your project. It has a very powerful and well-organized API.

compile 'org.altbeacon:android-beacon-library:2+@aar'

2) Add Bluetooth permissions in manifest.xml.

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

3) Initialize the BeaconManager and assign to him beacon layouts (URL, UID or/and TLM).

    private static final String BEACON_LAYOUT_URL = "s:0-1=feaa,m:2-2=10,p:3-3:-41,i:4-20v";
    private static final String BEACON_LAYOUT_UID = "s:0-1=feaa,m:2-2=00,p:3-3:-41,i:4-13,i:14-19";
    private static final String BEACON_LAYOUT_TLM = "x,s:0-1=feaa,m:2-2=20,d:3-3,d:4-5,d:6-7,d:8-11,d:12-15";


    // variables
    private BeaconManager mBeaconManager;

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

        initVariables();
        setBeaconLayouts();
    }

    @Override
    public void onResume() {
        super.onResume();
        mBeaconManager.bind(this);
    }

    @Override
    public void onPause() {
        super.onPause();
        mBeaconManager.unbind(this);
    }

    private void initVariables() {
        mBeaconManager = BeaconManager.getInstanceForApplication(this.getApplicationContext());
    }

    private void setBeaconLayouts() {
        // Detect the URL frame:
        mBeaconManager.getBeaconParsers().add(new BeaconParser().
                setBeaconLayout(BEACON_LAYOUT_URL));

        // Detect the main Eddystone-UID frame:
        mBeaconManager.getBeaconParsers().add(new BeaconParser().
                setBeaconLayout(BEACON_LAYOUT_UID));

        // Detect the telemetry Eddystone-TLM frame:
        mBeaconManager.getBeaconParsers().add(new BeaconParser().
                setBeaconLayout(BEACON_LAYOUT_TLM));
    }

4) In your “Scanner” Activity implement BeaconConsumer and RangeNotifier interfaces. Now, you are required to override the methods onBeaconServiceConnect (here you set the range) and didRangeBeaconsInRegion (here you add the beacons into your AdapterView/RecyclerView).

    @Override
    public void onBeaconServiceConnect() {
        Region region = new Region(REGION_ID, null, null, null);

        try {
            mBeaconManager.startRangingBeaconsInRegion(region);
        } catch (RemoteException e) {
            e.printStackTrace();
        }

        mBeaconManager.setRangeNotifier(this);
    }

    @Override
    public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
        for (final Beacon beacon: beacons) {
            if (!mBeaconAdapter.getData().contains(beacon)) {
                addBeaconToAdapter(beacon);
            }
        }
    }

5) In order to find beacons you have to turn on Bluetooth. Your decision is if this will happen due to button click or other event.

                    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);

6) Bonus: if you want to be precise in your app implementation add a BroadcastRecever which listens for changes in Bluetooth connection. See how we’ve done this:

public class BluetoothBroadcastReceiver extends BroadcastReceiver {

    // constants
    public static final String BASIC_TAG = BluetoothBroadcastReceiver.class.getName();

    // variables
    private IntentFilter mIntentFilter;
    private Context mContext;
    private BluetoothStateListener mCallback;

    // constructor
    public BluetoothBroadcastReceiver(Context context, BluetoothStateListener callback) {
        mContext = context;
        mCallback = callback;

        initVariables();
    }

    // methods
    @Override
    public void onReceive(Context context, Intent intent) {
        final String TAG = Util.stringsToPath(BASIC_TAG, "onReceive");
        String action = intent.getAction();

        LogUtil.log(TAG, String.format("Bluetooth action: %s", action));

        switch (action) {
            // Bluetooth state has changed.
            case BluetoothAdapter.ACTION_STATE_CHANGED: {
                int previousState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, -1);
                int currentState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);

                LogUtil.log(TAG, String.format("Bluetooth LE State Change. Current state: %d, Previous State: %d",
                        currentState, previousState));

                if (mCallback != null) {
                    if (previousState != BluetoothAdapter.STATE_ON &&
                            currentState == BluetoothAdapter.STATE_ON) {
                        mCallback.onBluetoothStateChanged(true);
                    }

                    if (previousState != BluetoothAdapter.STATE_OFF &&
                            currentState == BluetoothAdapter.STATE_OFF) {
                        mCallback.onBluetoothStateChanged(false);
                    }
                }

                break;
            }

            default: {
                LogUtil.log(TAG, "Error: Unhandled Bluetooth LE Intent");

                break;
            }
        }
    }

    public void turnOn() {
        mIntentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
        mContext.registerReceiver(this, mIntentFilter);
    }

    public void turnOff() {
        try {
            mContext.unregisterReceiver(this);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        }
    }

    private void initVariables() {
        mIntentFilter = new IntentFilter();
    }
}

7) You can check out the source code in Github here

We’ve published an app which follows these steps – Beascan

Comments are very welcome! Thank you!

Published by

Stefan Tsekov

Android developer @ Dision