androidibeaconaltbeaconibeacon-androidandroid-ibeacon

Android Beacon library background scanning via setIntentScanningStrategyEnabled(true) not detecting beacon


I would like my app to show a notification while app being killed, when my custom beacon appears in range. I am using latest Android beacon library, targeting Android 13.

I would like to let the library scan in background using intent strategy (beaconManager.setIntentScanningStrategyEnabled(true)). Job scheduler strategy is too slow for me. I also don't want foreground service persistent notification.

However, after calling beaconManager.setIntentScanningStrategyEnabled(true) my app doesn't detect any beacon.

I can detect my beacon perfectly just without this line. What do I need to do to try intent scanning feature?

A few logcat logs captured just after running app with intent based setup:

2023-05-13 17:17:59.258 17253-17318 MonitoringStatus        com.MichalKapuscinski.BikeTPMS       D  Time to purge inactive regions.
2023-05-13 17:17:59.259 17253-17318 ScanHelper              com.MichalKapuscinski.BikeTPMS       D  Calling ranging callback
2023-05-13 17:17:59.259 17253-17318 Callback                com.MichalKapuscinski.BikeTPMS       D  attempting callback via direct method call
2023-05-13 17:17:59.260 17253-17318 IntentHandler           com.MichalKapuscinski.BikeTPMS       D  got ranging data
2023-05-13 17:17:59.260 17253-17253 MainActivity            com.MichalKapuscinski.BikeTPMS       D  Ranged: 0 beacons
2023-05-13 17:17:59.260 17253-17253 MainActivity            com.MichalKapuscinski.BikeTPMS       D  Ranged: 0 beacons
2023-05-13 17:17:59.304 17253-17270 System                  com.MichalKapuscinski.BikeTPMS       W  A resource failed to call close. 

And with my app still in foreground, logs stops here, App says "0 beacon(s) detected.

My onCreate() code that fails to detect beacon (and detects it without line: setIntentScanningStrategyEnabled(true)):

    override fun onCreate() {
        super.onCreate()

        val beaconManager = BeaconManager.getInstanceForApplication(this)
        BeaconManager.setDebug(true)

        // If you don't care about AltBeacon, you can clear it from the defaults:
        beaconManager.getBeaconParsers().clear()

        beaconManager.getBeaconParsers().add(
            BeaconParser().setBeaconLayout("m:3-4=eaca,i:5-5,i:6-6,i:7-7,p:2-2,d:12-15"))   

        // By default, the library will scan in the background every 5 minutes on Android 4-7,
        // which will be limited to scan jobs scheduled every ~15 minutes on Android 8+
        // If you want more frequent scanning (requires a foreground service on Android 8+),
        // configure that here.
        // If you want to continuously range beacons in the background more often than every 15 mintues,
        // you can use the library's built-in foreground service to unlock this behavior on Android
        // 8+.   the method below shows how you set that up.
        //setupForegroundService()
        //beaconManager.setEnableScheduledScanJobs(true);
        //beaconManager.setBackgroundBetweenScanPeriod(0);
        //beaconManager.setBackgroundScanPeriod(1100);

        // Ranging callbacks will drop out if no beacons are detected
        // Monitoring callbacks will be delayed by up to 25 minutes on region exit
        beaconManager.setIntentScanningStrategyEnabled(true)

        // The code below will start "monitoring" for beacons matching the region definition below
        // the region definition is a wildcard that matches all beacons regardless of identifiers.
        // if you only want to detect beacons with a specific UUID, change the id1 paremeter to
        // a UUID like Identifier.parse("2F234454-CF6D-4A0F-ADF2-F4911BA9FFA6")
        region = Region("all-beacons", null, null, null)
        beaconManager.startMonitoring(region)
        beaconManager.startRangingBeacons(region)     // wysypuje apke
        // These two lines set up a Live Data observer so this Activity can get beacon data from the Application class
        val regionViewModel = BeaconManager.getInstanceForApplication(this).getRegionViewModel(region)
        // observer will be called each time the monitored regionState changes (inside vs. outside region)
        regionViewModel.regionState.observeForever( centralMonitoringObserver)
        // observer will be called each time a new list of beacons is ranged (typically ~1 second in the foreground)
        regionViewModel.rangedBeacons.observeForever( centralRangingObserver)
}

My manifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
    <!-- Below is only needed if you want to read the device name or establish a bluetooth connection
    -->
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>

    <application
        android:name="BeaconReferenceApplication"
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyApplication"
        tools:targetApi="31">

        <activity
            android:launchMode="singleInstance"
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name="com.MichalKapuscinski.BikeTPMS.beacon.permissions.BeaconScanPermissionsActivity"
            android:exported="false"/>
        </application>

</manifest>

Solution

  • Android supports different kinds of Bluetooth scans, some of which require a ScanFilter to offload the work of pattern-matching from the phone's CPU to the Bluetooth chip. For ScanFilters to work with Bluetooth LE manufacturer advertisements (like this advertisement format shown in the question: BeaconParser().setBeaconLayout("m:3-4=eaca,i:5-5,i:6-6,i:7-7,p:2-2,d:12-15"))) you must tell the Android Beacon Library what manufacturer code to use with the BeaconParser. Like this:

    val beaconParser =
             BeaconParser().setBeaconLayout("m:3-4=eaca,i:5-5,i:6-6,i:7-7,p:2-2,d:12-15")) 
    beaconParser.setHardwareAssistManufacturerCodes(intArrayOf(0x0100))
    

    The above code tells that library that the expected hardware manufacturer field for the BeaconParser is 0x0100 (TomTom International) per this list. The library will then use that manufacturer code to set up the proper ScanFilter for scan types that need it. (Like intent-backed scans of the IntentScanStrategy and background low powered filtered scans used between ScanJobs on Android 8+.)

    The reason you saw no detections with the Intent Scan Strategy (and no rapid background low power scan detections when using ScanJobs) is because this code was not set, so it used the default codes that do not include the one you want. This kept the ScanFilter from matching the advertisements and there were no detections.

    The reason most Android Beacon Library users don't need to do this is because for standard beacon formats using manufacturer advertisements like iBeacon and AltBeacon, the library sets this automatically. When using a custom or different manufacturer advertisement type, setting the manufacturer codes is necessary.