1

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.

Alon Minski
  • 1,571
  • 2
  • 19
  • 32
  • I would consider this a typical initialization problem. Obviously the foreign application uses different parameters than your own and since the Bluetooth hardware is only available once it shows different behavior depending on which application is started. No help without source code ;-) – Risto Nov 29 '22 at 10:21
  • @Risto I could only wish it was an issue with different parameters or configurations. But in reality, I simply copy pasted the same code from the sample app and changed nothing in it. I am looking for possible reasons the same code scans a beacon in one app and does not in a different project using the same code. – Alon Minski Nov 29 '22 at 11:31
  • Is KontaktAPI initialized with your API key? – Risto Nov 29 '22 at 12:09
  • Yes. I registered and grabbed the API key. Their example does not use any API key ( it is only ranging beacons) so I tried with out a key as well. – Alon Minski Nov 29 '22 at 12:36
  • You say the code is exactly the same between your app and Kontakt.io's sample app. That surely isn't quite true -- there is some subtle difference you need to find. I suspect the problem is related to the location permission on your app. I would uninstall and reinstall, re-grant the permissions and confirm in Settings -> Apps -> Your App that precise location (always) permission is granted and that nearby devices permission is granted. – davidgyoung Nov 29 '22 at 14:07
  • Hi @davidgyoung ... I actually been using your library for years and it is amazing :) But even with your library I encountered the same issue. When targeting API 31, our app that is using your lib stopped finding our beacons. I then used the sample app you provide and it too, the same as Kontak's sample app, managed to find the beacons. I also suspect it is something related to permissions. Not sure where or how though. I also tried polidea's RxBleClient. With the same results.I know the issue is in my app. Can't come up with a good direction to investigate. – Alon Minski Nov 29 '22 at 14:33
  • I actually copied all the permissions we use in our App with an almost identical flow of runtime permission requests... they still manage to scan for those beacons. – Alon Minski Nov 29 '22 at 14:34
  • Thank you guys for the time and effort btw. Highly appreciated... – Alon Minski Nov 29 '22 at 14:35

1 Answers1

0

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.

Alon Minski
  • 1,571
  • 2
  • 19
  • 32
  • Yes, this has bitten me before. I believe RxBleClient inserts a `neverForLocation=“true”` into the manifest BLUETOOTH_SCAN permission XML tag, which blocks detection of iBeacon and Eddystone on Android 12+. – davidgyoung Nov 29 '22 at 20:54