In this tutorial you will find out how to support the Fliegl Counter HD Beacons in your own iOS application in just a few steps.

The connection to the beacons is working with Bluetooth LE communication and is reserved to the end devices (and their new generations) that contain Bluetooth 4.0 interface and Android API platform 18 or more. This examples uses API v23+

Sample Image

To use the bluetooth interface you should add the code below to the manifest of the application, if it's not excisting yet.

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
package fliegl.com.tcb;

import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.ParcelUuid;
import android.support.v7.app.AppCompatActivity;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class TankDetailController extends AppCompatActivity {

    final static public UUID flieglCounterHD_UUID_1 = UUID.fromString("c13aaaa0-c497-4c95-8699-01b142af0c24");
    private static final String TAG = "Fliegl Counter HD: ";
    private static final int PERMISSION_REQUEST_COARSE_LOCATION = 1;

    private BluetoothAdapter mBluetoothAdapter;
    private int REQUEST_ENABLE_BT = 1;
    private Handler mHandler;
    private static final long SCAN_PERIOD = 30000;
    private BluetoothLeScanner mLEScanner;
    private ScanSettings settings;
    private List<ScanFilter> filters;
    private BluetoothGatt mGatt;

    private TextView logTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tank_detail_controller);

        logTextView = (TextView) findViewById(R.id.logTextView);
        logTextView.setText("Starting Example Application\n\n---\n ");
        logTextView.setMovementMethod(new ScrollingMovementMethod());

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            // Android M Permission check

            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 beacons.");
                builder.setPositiveButton(android.R.string.ok, null);
                builder.setOnDismissListener(new DialogInterface.OnDismissListener(){

                    public void onDismiss(DialogInterface dialog) {
                        requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSION_REQUEST_COARSE_LOCATION);
                    }
                });
                builder.show();
            }
        }

        mHandler = new Handler();
        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            Toast.makeText(this, "BLE Not Supported",
                    Toast.LENGTH_SHORT).show();
            finish();
        }
        final BluetoothManager bluetoothManager =
                (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = bluetoothManager.getAdapter();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           String permissions[],
                                           int[] grantResults) {
        switch (requestCode) {
            case PERMISSION_REQUEST_COARSE_LOCATION: {
                if (grantResults[0] == PackageManager.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;
            }
        }
    }

    private List<ScanFilter> scanFilters(UUID[] serviceUUIDs) {
        List<ScanFilter> list = new ArrayList<>();
        /*for (int i = 0; i < serviceUUIDs.length; i++) {
            ScanFilter filter = new ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString(serviceUUIDs[i].toString())).build();
            list.add(filter);
        }*/
        return list;
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
        } else {
            if (Build.VERSION.SDK_INT >= 21) {
                mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
                settings = new ScanSettings.Builder()
                        .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
                        .build();
                UUID[] myKnownServices = {flieglCounterHD_UUID_1};
                filters = scanFilters(myKnownServices);
            }
            scanLeDevice(true);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) {
            scanLeDevice(false);
        }
    }

    @Override
    protected void onDestroy() {
        if (mGatt == null) {
            return;
        }
        mGatt.close();
        mGatt = null;
        super.onDestroy();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_ENABLE_BT) {
            if (resultCode == Activity.RESULT_CANCELED) {
                //Bluetooth not enabled.
                finish();
                return;
            }
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

    private void scanLeDevice(final boolean enable) {
        if (enable) {
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mLEScanner.stopScan(mScanCallback);
                }
            }, SCAN_PERIOD);
            mLEScanner.startScan(filters, settings, mScanCallback);
        } else {
            mLEScanner.stopScan(mScanCallback);
        }
    }

    private ScanCallback mScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            BluetoothDevice btDevice = result.getDevice();

            List<ParcelUuid> uuids = result.getScanRecord().getServiceUuids();
            if(uuids != null)
            {
                if(!uuids.isEmpty())
                {
                    for(ParcelUuid uid : uuids)
                    {
                        if (uid.getUuid().toString().equalsIgnoreCase(flieglCounterHD_UUID_1.toString()))
                        {
                            if (btDevice.getName().length() > 0)
                                updateLogTextInUserInterface(btDevice.getName());
                            Log.i(TAG, "\n\n---> Found Counter Service UUID: \n" + uid.getUuid().toString());
                            updateLogTextInUserInterface("\nFound Counter Service UUID: \n" + uid.getUuid().toString());
                            connectToDevice(btDevice);
                        }
                    }
                }
            }
        }

        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            for (ScanResult sr : results) {
            }
        }

        @Override
        public void onScanFailed(int errorCode) {
        }
    };

    public void connectToDevice(BluetoothDevice device) {
        if (mGatt == null) {
            mGatt = device.connectGatt(this, false, gattCallback);
            scanLeDevice(false);// will stop after first device detection
        }
    }

    private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            switch (newState) {
                case BluetoothProfile.STATE_CONNECTED:
                    Log.i(TAG, "STATE_CONNECTED");
                    updateLogTextInUserInterface("State --> Connected");
                    gatt.discoverServices();
                    break;
                case BluetoothProfile.STATE_DISCONNECTED:
                    Log.e(TAG, "STATE_DISCONNECTED");
                    updateLogTextInUserInterface("State --> Disconnected");
                    scanLeDevice(false);
                    break;
                default:
                    Log.e(TAG, "STATE_OTHER");
                    updateLogTextInUserInterface("State --> Other");
            }

        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            List<BluetoothGattService> services = gatt.getServices();
            Log.i(TAG, services.toString());
            for(BluetoothGattService s : services)
            {
                Log.i(TAG ,"--> Discovered Service: \n" + s.getUuid().toString());
                updateLogTextInUserInterface("--> Discovered Service: \n" + s.getUuid().toString());

                for(BluetoothGattCharacteristic c : s.getCharacteristics())
                {
                    Log.i(TAG ,"--> Found Characteristic: " + c.getUuid().toString());
                    updateLogTextInUserInterface("--> Found Characteristic: \n" + c.getUuid().toString());
                    gatt.readCharacteristic(c);
                }
            }
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt,
                                         BluetoothGattCharacteristic
                                                 characteristic, int status) {
            Log.i(TAG,"--> Read characteristic: \n" + characteristic.getUuid().toString());
            updateLogTextInUserInterface("--> Read characteristic: \n" + characteristic.getUuid().toString());
            Log.i(TAG,"--> Characteristic Value: \n" + characteristic.getValue().toString());
            updateLogTextInUserInterface("--> Characteristic Value: \n" + characteristic.getValue().toString());
        }
    };

    public void updateLogTextInUserInterface(final String appendingString)
    {
        runOnUiThread(new Runnable() {
            public void run() {
                logTextView.append("\n" + appendingString);
            }
        });
    }
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="fliegl.com.tcb.TankDetailController"
    tools:showIn="@layout/activity_tank_detail_controller"
    android:scrollbars="vertical">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentTop="true"
        android:layout_alignParentStart="true">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Fliegl Counter HD Example"
            android:layout_alignParentTop="true"
            android:layout_alignParentStart="true"
            android:textStyle="normal|bold"
            android:textAlignment="center" />

        <TextView
            android:text="TextView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/logTextView"
            android:editable="false"
            android:layout_marginTop="17dp"
            android:layout_below="@+id/textView"
            android:layout_alignParentStart="true"
            android:scrollbarAlwaysDrawHorizontalTrack="true"
            android:scrollbars="vertical"
            android:scrollbarStyle="insideOverlay"
            android:scrollIndicators="right|start|end"
            android:scrollHorizontally="true"
            android:scrollbarAlwaysDrawVerticalTrack="true" />
    </LinearLayout>

</RelativeLayout>

Of course we give you the example for the download for Android Studio.

Previous Post