AltBeacon / android-beacon-library

Allows Android apps to interact with BLE beacons
Apache License 2.0
2.83k stars 834 forks source link

Improved Settings and Region configuration APIs #1061

Open davidgyoung opened 2 years ago

davidgyoung commented 2 years ago

The Problem

Configuring the library has evolved over time with a mixed bag of styles of settings including:

In addition, changing settings at runtime (after scanning has started) can sometimes cause problems and behavior is either poorly documented, non-deterministic, or at worst cause crashes. Even in the best of cases, changes to not necessarily take effect right away, and it is unclear to users what does take effect right away and what does not.

Constraints

This proposal for improvement in the library API is designed to stay within the following constraints:

  1. Make it easier for library users to configure library settings
  2. Retain existing settings API (temporarily for 2.x library versions with API marked as deprecated) for backward compatibility.
  3. Only support the new settings API in 3.0+ library releases

Settings API Proposal

The Settings Class below is used to set up library scanning settings. These are typically set before starting ranging/monitoring. If you want to change them after ranging and monitoring are already started, the settings change will stop and restart ranging and monitoring.

The class design below is designed to use a Kotlin data class with a Builder class designed for use by Java apps

Kotlin:

        val settings = Settings(
            debug = true,
            distanceModelUpdateUrl = "https://s3.amazonaws.com/android-beacon-library/android-distance.json",
            scanStrategy = Settings.ForegroundServiceScanStrategy(
                Notification.Builder(
                    context,
                    "BeaconReferenceApp"
                ).build(), 1
            ),
            longScanForcingEnabled = true,
            scanPeriods = Settings.ScanPeriods(
                foregroundScanPeriodMillis = 1100,
                foregroundBetweenScanPeriodMillis = 0,
                backgroundScanPeriodMillis = 1100,
                backgroundBetweenScanPeriodMillis = 0))
        beaconManager.adjustSettings(settings)

Java:

        Settings settings = new Settings.Builder()
                .setDebug(true)
                .setDistanceModelUpdateUrl("https://s3.amazonaws.com/android-beacon-library/android-distance.json")
                .setScanPeriods(new Settings.ScanPeriods(1100, 0, 10000, 0))
                .setScanStrategy(new Settings.ForegroundServiceScanStrategy(
                        new Notification.Builder(context, "BeaconReferenceApp").build(),1)
                )
                .setLongScanForcingEnabled(true)
                .build();
        beaconManager.adjustSettings(settings); 

There are three methods added to BeaconManager for applying a Settings object:

  1. adjustSettings(settings) // Applies Applies settings fields explicitly configured on the passed settings object and any fields not configured are left to what they were before this call. This effectively applies a delta
  2. replaceSettings(settings) // Applies settings fields explicitly configured on the passed settings object and any fields not configured are set to library defaults
  3. revertSettings() // Reverts all previously applied settings in favor of library defaults

Configure scheduled job strategy (default on Android 8+)

        val settings = Settings(
            scanStrategy = Settings.JobServiceScanStrategy(
                immediateJobId = 1234,
                periodicJobId = 1235,
                jobPersistenceEnabled = true
            ),
            longScanForcingEnabled = true,
            scanPeriods = Settings.ScanPeriods(
                foregroundScanPeriodMillis = 1100,
                foregroundBetweenScanPeriodMillis = 0,
                backgroundScanPeriodMillis = 30000,
                backgroundBetweenScanPeriodMillis = 300000
            )
        )
        beaconManager.adjustSettings(settings)

Configure background service strategy (default on Android 4.3-7.x)

        val settings = Settings(
            scanStrategy = Settings.BackgroundServiceScanStrategy()
        )
        beaconManager.adjustSettings(settings)

Configure foreground service strategy

        val settings = Settings(
            scanStrategy = Settings.ForegroundServiceScanStrategy(
                Notification.Builder(
                    context,
                    "BeaconReferenceApp"
                ).build(), 1
            ),
            longScanForcingEnabled = true,
            scanPeriods = Settings.ScanPeriods(
                foregroundScanPeriodMillis = 1100,
                foregroundBetweenScanPeriodMillis = 0,
                backgroundScanPeriodMillis = 1100,
                backgroundBetweenScanPeriodMillis = 0))
        beaconManager.adjustSettings(settings)

Configure intent scan strategy

        val settings = Settings(
            scanStrategy = Settings.IntentScanStrategy(),
            longScanForcingEnabled = true,
            scanPeriods = Settings.ScanPeriods(
                foregroundScanPeriodMillis = 1100,
                foregroundBetweenScanPeriodMillis = 0,
                backgroundScanPeriodMillis = 1100,
                backgroundBetweenScanPeriodMillis = 0)
        )
        beaconManager.adjustSettings(settings)

Deprecated settings that will not be supported by new API

beaconManager.setScannerInSameProcess()
beaconManager.setDataRequestNotifier()
beaconManager.getBeaconParsers()
beaconManager.isAndroidLScanningDisabled()

New BeaconParsers and Region API

Instead of manually configuring BeaconParsers on the BeaconManager using the beaconManager.getBeaconParsers() collection, a new Region class called BeaconRegion will explicitly set the BeaconParser used for each region. Starting ranging or monitoring will auto-register the parser with the library if it has not already been registered.

The point of this change is to make it pretty much impossible to forget to register a parser before starting ranging/monitoring. This also makes it explicit which beacon type you are looking for with the region, and only beacons of that type will match the region.

While proprietary closed-source beacon types cannot be configured in the AltBeacon project due to intellectual property restrictions, external libraries can define them. Referencing these external libraries with the parser definitions makes it much easier to configure apps to work with proprietary beacons:

build.gradle:

dependencies {
    implementation 'com.davidgyoungtech:beacon-parsers:1.0'  // Defines IBeacon and other proprietary formats
    implementation 'org.altbeacon:android-beacon-library:3.0'  // This library without proprietary beacon support

Start ranging:

    val iBeaconRegion = BeaconRegion("all-ibeacons", IBeaconParser(), null, null, null)
    beaconManager.startRangingBeacons(iBeaconRegion)

Note in the above that there is a new second parameter in the region constructor for the BeaconParser. This is parser is activated in the library the first time ranging or monitoring is started as shown in the second line.

davidgyoung commented 2 months ago

The redesign needs to replace and deprecate the BeaconManager configuration method public static void setRssiFilterImplClass(@NonNull Class c) due to issues in #1164. The RssiFilter interface will hopefully remain unchanged.

davidgyoung commented 2 months ago

Work in progress PR is here: #1177