Search code examples
bluetoothandroid-permissionsbeaconandroid-api-31kontakt.io

Android 12 - Beacon scanning... weirdest thing


Ok so here is my situation:

I have my app installed. It is now targeting Android 12. I also have Kontakt.io Android sample app installed.

When I use Kontakt.io's sample app to scan for beacons, it works. Among other devices, I can see my beacons appear in the list and logs.

When I use the same exact code (from their sample app) in my app with exactly the same configuration and exactly the same permission scheme I am unable to scan my beacons. I can see in Logcat there are a lot of other devices that are being scanned and reported but none of my beacons are scanned.

Then - and here is the weird thing - while my app is in the background, I switch back to Kontakt.io sample app and initiate a new scan. The second I start that scan, MY app suddenly starts seeing my beacons and I can see them in the logs. If I shut down that Kontakt.io sample app scan, I can still see the beacons appear in my app's scan. If I kill both apps and try to scan again in my app alone, again, I cannot see my beacons any more.

I've been struggling with this for days now and it simply does not make any sense. I'll add here that if I do not target API 31 (Android 12) everything is perfect. As it's been for the past few years.

Please! Any ideas?

EDIT1: adding some source code

The code is identical to the one in Kontakt.io's sample app. It is a simple copy paste:

In an activity, on a button click I call checkPermissions():

private void checkPermissions() {
        String[] requiredPermissions = Build.VERSION.SDK_INT < Build.VERSION_CODES.S
                ? new String[]{Manifest.permission.ACCESS_FINE_LOCATION}
                : new String[]{ Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.ACCESS_FINE_LOCATION };
        if(isAnyOfPermissionsNotGranted(requiredPermissions)) {
            ActivityCompat.requestPermissions(getScreen().getHostingActivity(), requiredPermissions, 100);
        } else {
            setupProximityManager();
            setupSpaces();
        }
    }

private void setupProximityManager() {
    proximityManager = ProximityManagerFactory.create(this);

    //Configure proximity manager basic options
    proximityManager.configuration()
        //Using ranging for continuous scanning or MONITORING for scanning with intervals
        .scanPeriod(ScanPeriod.RANGING)
        //Using BALANCED for best performance/battery ratio
        .scanMode(ScanMode.BALANCED)
        //OnDeviceUpdate callback will be received with 5 seconds interval
        .deviceUpdateCallbackInterval(TimeUnit.SECONDS.toMillis(5));

    //Setting up iBeacon and Eddystone listeners
    proximityManager.setIBeaconListener(createIBeaconListener());
    proximityManager.setEddystoneListener(createEddystoneListener());
  }
private void setupSpaces() {
        //Setting up single iBeacon region. Put your own desired values here.
        IBeaconRegion region = new BeaconRegion.Builder().identifier("My Region") //Region identifier is mandatory.
                .proximity(UUID.fromString("f7826da6-4fa2-4e98-8024-bc5b71e0893e")) //Default Kontakt.io proximity.
                //Optional major and minor values
                //.major(1)
                //.minor(1)
                .build();

        proximityManager.spaces().iBeaconRegion(region)
                .forceResolveRegions(Collections.singleton(UUID.fromString("f7826da6-4fa2-4e98-8024-bc5b71e0893e")));

        //Setting up single Eddystone namespace. Put your own desired values here.
        IEddystoneNamespace namespace = new EddystoneNamespace.Builder().identifier("My Namespace") //Namespace identifier is mandatory.
                .namespace("f7826da64fa24e988024") //Default Kontakt.io namespace.
                //Optional instance id value
                //.instanceId("instanceId")
                .build();
        proximityManager.spaces().eddystoneNamespace(namespace).forceResolveNamespaces(Collections.singletonList("f7826da64fa24e988024"));
    }

On a different button click I call:

private void startScanning() {
        //Connect to scanning service and start scanning when ready
        proximityManager.connect(new OnServiceReadyListener() {
            @Override
            public void onServiceReady() {
                //Check if proximity manager is already scanning
                if (proximityManager.isScanning()) {
                    Toast.makeText(((Application)Application.getContextFromApplicationClass()).getActivityContext(), "Already scanning", Toast.LENGTH_SHORT).show();
                    return;
                }
                proximityManager.startScanning();
//                progressBar.setVisibility(View.VISIBLE);
                Toast.makeText(((Application)Application.getContextFromApplicationClass()).getActivityContext(), "Scanning started", Toast.LENGTH_SHORT).show();
            }
        });
    }

androidmanifest.xml contains the needed permissions (copied from the sample app):

<uses-permission android:name="android.permission.BLUETOOTH"
        android:maxSdkVersion="30"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
        android:maxSdkVersion="30"/>
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

I already have a foreground service in my app if it makes any difference.

I also declared as instructed another service that is used by the Kontakt SDK:

<service
            android:name="com.kontakt.sdk.android.ble.service.ProximityService"
            android:exported="false" />

All the runtime permissions are requested in the same manner they are requested in the Kontakt.io sample app and the actual permissionCheck passes.


Solution

  • Ok... so I found the cause. For years our app used 2 libraries: RxBleClient for scanning and AltBeacon for parsing the result. Not entirely sure why use 2 libs but that was the case. When we targeted API 12, I assume both libs overwrote some of the androidmanifest.xml permission declarations during merge in some manner. I did not dive deep into the actual cause, but this seems to be the issue. At any rate, to resolve the issue, we removed ALL libs that relate to beacon scanning and put a single one. Currently we picked Kontakt.io SDK. Scanning is working now perfectly. This also explains why on their own, each sample app from each library was working as expected. But when you combine more than 1 lib and target api 31, they seem to interfere with each other and the "delicate" permissions required for BLE scanning in android. This problem took me waaaay to long to solve.