Configuration.kt

This is a container, presenting a common interface from potential multiple configurations. DaemonConfig is an example of a configuration creator.
package my.package.ble

import android.app.Activity
import fr.maxcom.beacon.configuration.ScanFrequency
import fr.maxcom.beacon.configuration.ScanMode
import fr.maxcom.beacon.manager.listeners.AScanStatusListener
import fr.maxcom.beacon.plugin.IExplorerPlugin
import fr.maxcom.beacon.plugin.IParserPlugin
import fr.maxcom.beacon.plugin.IProcessorPlugin
import fr.maxcom.beacon.processor.IProcessor
import fr.maxcom.beacon.rssi.IRssiCalculator
import java.util.UUID
import kotlin.concurrent.Volatile

class Configuration {
    // general settings ...

    var verbose = false

    // dedicated to ScanManager ...

    var activityClass: Class<out Activity>? = null
    var scanStatusListener: AScanStatusListener? = null

    // dedicated to the scan processing ...

    var scanFrequency: ScanFrequency? = null
    var scanMode: ScanMode? = null
    var rssiCalculator: IRssiCalculator? = null
    var preventDoze = false
    var reminderIntervalSeconds: Long = 0
    var devicesUpdateEventSlowdown: Long = -1 // special value meaning 'not set', for the default value to apply

    // dedicated to feed a IContextRegistrar (typically a ScanContext.Builder) ...

    private var mProcessors: MutableList<IProcessor>? = null
    private var mProcessorPlugins: MutableList<IProcessorPlugin>? = null
    private var mExplorerPlugins: MutableList<IExplorerPlugin>? = null
    private var mParserPlugins: MutableList<IParserPlugin>? = null

    // setters dedicated to the main app, thus concentrating all library accesses to only a ble subpackage ...

    @Suppress("unused")
    fun setRangingScanFrequency() {
        scanFrequency = ScanFrequency.RANGING
    }
    @Suppress("unused")
    fun setMonitoringScanFrequency() {
        scanFrequency = ScanFrequency.MONITORING
    }

    // collections management for processors and plugins ...

    fun addProcessor(processor: IProcessor): Configuration {
        if (mProcessors == null) mProcessors = ArrayList()
        mProcessors!!.add(processor)
        return this
    }

    val processors: List<IProcessor>?
        get() = mProcessors

    @Suppress("unused")
    fun addProcessorPlugin(plugin: IProcessorPlugin): Configuration {
        if (mProcessorPlugins == null) mProcessorPlugins = ArrayList()
        mProcessorPlugins!!.add(plugin)
        return this
    }

    val processorPlugins: List<IProcessorPlugin>?
        get() = mProcessorPlugins

    @Suppress("unused")
    fun addExplorerPlugin(plugin: IExplorerPlugin): Configuration {
        if (mExplorerPlugins == null) mExplorerPlugins = ArrayList()
        mExplorerPlugins!!.add(plugin)
        return this
    }

    val explorerPlugins: List<IExplorerPlugin>?
        get() = mExplorerPlugins

    @Suppress("unused")
    fun addParserPlugin(plugin: IParserPlugin): Configuration {
        if (mParserPlugins == null) mParserPlugins = ArrayList()
        mParserPlugins!!.add(plugin)
        return this
    }

    val parserPlugins: List<IParserPlugin>?
        get() = mParserPlugins

    // cleanup of a possible previous run
    fun clear() {
        rssiCalculator?.clear()
        mProcessors?.forEach { it.clear() }
        mProcessorPlugins?.forEach { it.clear() }
        mExplorerPlugins?.forEach { it.clear() }
    }

    companion object {
        // the default iBeacon proximity UUID of interest for the app
        val APP_UUID: UUID = UUID.fromString("01020304-0506-0708-090a-0b0c0d0e0f10") // TODO set your own one

        @Volatile
        private var sInstance: Configuration? = null
        private val SINGLETON_LOCK = Any()
        val instance: Configuration // One global static instance
            get() {
                var instance = sInstance
                if (instance == null) {
                    synchronized(SINGLETON_LOCK) {
                        instance = sInstance
                        if (instance == null) {
                            instance = Configuration()
                            sInstance = instance
                        }
                    }
                }
                return instance!!
            }
    }
}

DaemonConfig.kt

A configuration creator, for an automated usage, that is without feedback to a user.
package my.package.ble

import android.app.Activity
import android.content.Context
import android.util.Log
import fr.maxcom.beacon.configuration.DeviceFilter
import fr.maxcom.beacon.configuration.ScanMode
import fr.maxcom.beacon.device.BeaconDevice
import fr.maxcom.beacon.device.BeaconRegion
import fr.maxcom.beacon.device.EddystoneDevice
import fr.maxcom.beacon.device.EddystoneNamespace
import fr.maxcom.beacon.error.ScanError
import fr.maxcom.beacon.manager.listeners.IDeviceListener
import fr.maxcom.beacon.manager.listeners.AScanStatusListener
import fr.maxcom.beacon.manager.listeners.ISpaceListener
import fr.maxcom.beacon.plugin.IExplorerPlugin
import fr.maxcom.beacon.plugin.IPluginDeviceListener
import fr.maxcom.beacon.plugin.IProcessorPlugin
import fr.maxcom.beacon.plugin.manufacturer.AccSysParserPlugin
import fr.maxcom.beacon.plugin.manufacturer.ElaParserPlugin
import fr.maxcom.beacon.plugin.manufacturer.InvirtusProcessorPlugin
import fr.maxcom.beacon.plugin.manufacturer.MinewExplorerPlugin
import fr.maxcom.beacon.processor.BeaconProcessor
import fr.maxcom.beacon.processor.EddystoneProcessor
import fr.maxcom.beacon.processor.IProcessor
import fr.maxcom.beacon.rssi.CappedAverageRssiCalculator
import my.package.BuildConfig
import java.util.StringJoiner
import java.util.UUID

object DaemonConfig {
    private val TAG = DaemonConfig::class.java.simpleName

    // to call at the application creation, MainApplication/onCreate()
    fun init(
        context: Context,
        eventListener: IEventListener,
        activityClass: Class<out Activity?>
    ) {
        if (BuildConfig.DEBUG) Log.d(TAG, "init")
        val cfg = Configuration.instance
        with(cfg) {
            this.activityClass = activityClass
            //scanFrequency = ScanFrequency.MONITORING // default is ScanFrequency.RANGING
            scanMode = ScanMode.LOW_LATENCY // default is BALANCED
            rssiCalculator = CappedAverageRssiCalculator(5)
            reminderIntervalSeconds = 10 * 60
            devicesUpdateEventSlowdown = 14 * 1000

            scanStatusListener = object : AScanStatusListener {
                override fun onScanStart() {
                    Log.i(TAG, "onScanStart")
                }

                override fun onScanStop() {
                    Log.i(TAG, "onScanStop")
                }

                override fun onScanFailed(errorCode: Int, errorReason: String) {
                    // is already in logcat
                }

                override fun onScanError(scanError: ScanError) {
                    // is already in logcat
                }

                // onCycle*() are called only if ScanFrequency is set to something other than RANGING

                override fun onCycleResume() {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onCycleResume")
                }

                override fun onCyclePause() {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onCyclePause")
                }
            }
        }

        // may be recycled to serve more than one time
        val filters: MutableList<DeviceFilter> = ArrayList()

        // iBeacon settings
        val beaconProcessor = BeaconProcessor(
            object : IDeviceListener<BeaconDevice, BeaconRegion> {
                override fun onDiscovered(device: BeaconDevice, space: BeaconRegion) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onDiscovered: $device, region: $space")
                    writeEvent("+", device)
                }

                override fun onKnown(device: BeaconDevice, space: BeaconRegion) {
                    if (BuildConfig.DEBUG) Log.v(TAG, "onKnown: $device, region: $space")
                    writeEvent("=", device)
                }

                override fun onKnownReminder(device: BeaconDevice, space: BeaconRegion) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onKnownReminder: $device, region: $space")
                    writeEvent(".", device)
                }

                override fun onUpdated(devices: List<BeaconDevice>, space: BeaconRegion) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onUpdated: $devices, region: $space")
                }

                override fun onLost(device: BeaconDevice, space: BeaconRegion) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onLost: $device, region: $space")
                    writeEvent("-", device)
                }

                private fun writeEvent(type: String, device: BeaconDevice) {
                    val now = System.currentTimeMillis()
                    val sj = StringJoiner(",")
                    sj
                        .add(now.toString()) // long
                        .add(type)
                        .add(device.major.toString()) // int
                        .add(device.minor.toString()) // int
                        .add(device.distance.toString()) // int
                    val extras: Map<String, String> = device.extras
                    if (extras.isNotEmpty()) for ((key, value) in extras) sj.add(key + "_" + value)
                    eventListener.onEvent(sj.toString())
                }
            },
            object : ISpaceListener<BeaconRegion> {
                override fun onEntered(space: BeaconRegion) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onEntered: $space")
                }

                override fun onExited(space: BeaconRegion) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onExited: $space")
                }
            }
        )
        // if the processor has eventually an empty collection, BeaconRegion.EVERYWHERE will be assumed
        val regions: MutableCollection<BeaconRegion> = HashSet()
        regions.add(BeaconRegion.Builder()
            .name("MyApp") // TODO set your own value
            .proximity(Configuration.APP_UUID)
            .build())
        // optionally add some regions, for example to observe a few extra development beacon tags
        /*
        Example for an 'Ultra-Thin Beacon U1' tag from Meeblue, Inc.:
        regions.add(BeaconRegion.Builder()
            .name("Meeblue")
            .proximity(UUID.fromString("d35b76e2-e01c-9fac-ba8d-7ce20bdba0c6"))
            .build())
        Example for a tag from Minew Technologies Co.:
        regions.add(BeaconRegion.Builder()
            .name("Minew")
            .proximity(UUID.fromString("e2c56db5-dffb-48d2-b060-d0f5a71096e0"))
            .build())
        */
        beaconProcessor.setBeaconRegions(regions)

        // optionally set some filters, in particular in development phase to avoid being flooded with too many frames
        filters.clear()
        filters.add(DeviceFilter().setDeviceName("L ID 003D4F")) // Example of a 'BLUE LITE ID' tag from Ela Innovation
        filters.add(DeviceFilter().setDeviceName("S ID 002A3B")) // Example of a 'BLUE SLIM ID' tag from Ela Innovation
        filters.add(DeviceFilter().setDeviceAddress("AC:23:3F:A1:B2:C3")) // Example of a tag from Minew Technologies
        (beaconProcessor as IProcessor).setDeviceFilters(filters)

        cfg.addProcessor(beaconProcessor)

        // Eddystone settings
        val eddystoneProcessor = EddystoneProcessor(
            object : IDeviceListener<EddystoneDevice, EddystoneNamespace> {
                override fun onDiscovered(device: EddystoneDevice, space: EddystoneNamespace) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onDiscovered: $device, namespace: $space")
                    writeEvent("+", device)
                }

                override fun onKnown(device: EddystoneDevice, space: EddystoneNamespace) {
                    if (BuildConfig.DEBUG) Log.v(TAG, "onKnown: $device, namespace: $space")
                    writeEvent("=", device)
                }

                override fun onKnownReminder(device: EddystoneDevice, space: EddystoneNamespace) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onKnownReminder: $device, namespace: $space")
                    writeEvent(".", device)
                }

                override fun onUpdated(devices: List<EddystoneDevice>, space: EddystoneNamespace) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onUpdated: $devices, namespace: $space")
                }

                override fun onLost(device: EddystoneDevice, space: EddystoneNamespace) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onLost: $device, namespace: $space")
                    writeEvent("-", device)
                }

                private fun writeEvent(type: String, device: EddystoneDevice) {
                    val now = System.currentTimeMillis()
                    val sj = StringJoiner(",")
                    sj
                        .add(now.toString()) // long
                        .add(type)
                        .add(device.instance)
                        .add(device.distance.toString()) // int
                    val extras: Map<String, String> = device.extras
                    if (extras.isNotEmpty()) for ((key, value) in extras) sj.add(key + "_" + value)
                    eventListener.onEvent(sj.toString())
                }
            },
            object : ISpaceListener<EddystoneNamespace> {
                override fun onEntered(space: EddystoneNamespace) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onEntered: $space")
                }

                override fun onExited(space: EddystoneNamespace) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onExited: $space")
                }
            }
        )
        // if the processor has eventually an empty collection, EddystoneNamespace.EVERYWHERE will be assumed
        val namespaces: MutableCollection<EddystoneNamespace> = HashSet()
        namespaces.add(EddystoneNamespace.Builder()
            .name("MyApp") // TODO set your own value
            .namespace("00112233445566778899") // TODO set your own value
            .build())
        eddystoneProcessor.setEddystoneNamespaces(namespaces)

        // optionally set some filters, in particular in development phase to avoid being flooded with too many frames
        filters.clear()
        filters.add(DeviceFilter().setDeviceName("L ID 003D4F")) // Example of a 'BLUE LITE ID' tag from Ela Innovation
        filters.add(DeviceFilter().setDeviceName("S ID 002A3B")) // Example of a 'BLUE SLIM ID' tag from Ela Innovation
        filters.add(DeviceFilter().setDeviceAddress("AC:23:3F:A1:B2:C3")) // Example of a tag from Minew Technologies
        (eddystoneProcessor as IProcessor).setDeviceFilters(filters)

        cfg.addProcessor(eddystoneProcessor)

        // Plugins settings, if wanted ...

        // Processor Plugin
        val invirtusPlugin = InvirtusProcessorPlugin(
            object : IPluginDeviceListener<InvirtusProcessorPlugin.Device> {
                override fun onDiscovered(device: InvirtusProcessorPlugin.Device) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onDiscovered: $device")
                }

                override fun onUpdated(device: InvirtusProcessorPlugin.Device) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onUpdated: $device")
                }

                override fun onLost(device: InvirtusProcessorPlugin.Device) {
                    if (BuildConfig.DEBUG) Log.d(TAG, "onLost: $device")
                }
            }
        )
        // optionally set some filters, in particular in development phase to avoid being flooded with too many frames
        filters.clear()
        filters.add(DeviceFilter().setDeviceAddress("F2:18:FF:A1:B2:C3"))
        (invirtusPlugin as IProcessorPlugin).setDeviceFilters(filters)
        cfg.addProcessorPlugin(invirtusPlugin)

        // Explorer Plugin
        val minewPlugin = MinewExplorerPlugin()
        // optionally set some filters, in particular in development phase to avoid being flooded with too many frames
        filters.clear()
        filters.add(DeviceFilter().setDeviceAddress("AC:23:3F:A1:B2:C3"))
        (minewPlugin as IExplorerPlugin).setDeviceFilters(filters)
        cfg.addExplorerPlugin(minewPlugin)

        // Parser Plugins
        val elaPlugin = ElaParserPlugin()
        val accSysPlugin = AccSysParserPlugin()
        cfg.addParserPlugin(elaPlugin)
            .addParserPlugin(accSysPlugin)
    }
}