RobertT
RobertT

Reputation: 77

altbeacon BluetoothMedic crashes with Android version 12 + 13

The problems started when I updated Android to target SDK 31.

First, I had error in manifest because there was a receiver in a library that did not have android:exported set. This turned out to be the org.altbeacon.android-beacon-library. Fixed by updating to latest non-beta version, 2.19.4

Next, I had to add in the new permissions for Bluetooth to my manifest.

<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>

And also adjust the old permissions.

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

and also added the feature settings.

<uses-feature android:name="android.hardware.bluetooth"
        android:required="false"/>

    <uses-feature android:name="android.hardware.bluetooth_le"
        android:required="false"/>

In the app I'm asking the user for permissions. (this is the one used if the android release version is 12 or greater).

if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN)
                    != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_ADVERTISE)
                    != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT)
                    != PackageManager.PERMISSION_GRANTED) {
                FirebaseCrashlytics.getInstance().log("requesting permissions.");
                ActivityCompat.requestPermissions(this, new String[]
                        {Manifest.permission.BLUETOOTH_SCAN,
                                Manifest.permission.BLUETOOTH_CONNECT,
                                Manifest.permission.BLUETOOTH_ADVERTISE,
                        }, 22);
                } 

Note that I have quite a few of those Crashlytics logs debug messages throughout the program.

So, on some devices I am getting various crashes in the altbeacon library. Example:

Fatal Exception: java.lang.SecurityException: Need android.permission.BLUETOOTH_ADVERTISE permission for android.content.AttributionSource@881430fb: GattService startAdvertisingSet
       at com.android.bluetooth.Utils.checkPermissionForDataDelivery(Utils.java:482)
       at com.android.bluetooth.Utils.checkAdvertisePermissionForDataDelivery(Utils.java:570)
       at com.android.bluetooth.gatt.GattService.startAdvertisingSet(GattService.java:3252)
       at com.android.bluetooth.gatt.GattService$BluetoothGattBinder.startAdvertisingSet(GattService.java:1392)
       at com.android.bluetooth.gatt.GattService$BluetoothGattBinder.startAdvertisingSet(GattService.java:1376)
       at android.bluetooth.IBluetoothGatt$Stub.onTransact(IBluetoothGatt.java:362)
       at android.os.Binder.execTransactInternal(Binder.java:1285)
       at android.os.Binder.execTransact(Binder.java:1244)

Also getting BLUETOOTH_SCAN permissions crashes of the same general type.

1: All of the crashes are happening within 1 to 5 seconds of opening the app.

2: I am not getting any logs in Crashlytics, despite setting the log messages. I am wondering if the log function works if the crash occurs in a library?

3: The Crashlytics console shows that 99% of these crashes are happening on Samsung devices.

4: I can run this on a test phone with Android 12, that is not a Samsung, and it works fine. If I go into permissions settings for the app and turn off "nearby devices" the next time it runs it will ask me for permissions again and if I deny them it will run without the Bluetooth functions.

Upvotes: 0

Views: 589

Answers (1)

davidgyoung
davidgyoung

Reputation: 64916

For apps targeting SDK 31+ and running on Android 12+, the new Manifest.permission.BLUETOOTH_ADVERTISE permission must be grated by the user or any attempt to start Bluetooth Advertising (including by BluetoothMedic in the AndroidBeaconLibrary) will cause a fatal app exception, java.lang.SecurityException: Need android.permission.BLUETOOTH_ADVERTISE permission.

You can prevent this from happening one of two ways:

  1. Before enabling the BluetoothMedic to do a transmitter test like shown below, ensure the user has granted this permission.
if (ContextCompat.checkSelfPermission(context, android.permission.BLUETOOTH_ADVERTISE) == 
    PackageManager.PERMISSION_GRANTED) {
  BluetoothMedic.getInstance().enablePeriodicTests(this, BluetoothMedic.TRANSMIT_TEST)`
}
else {
  BluetoothMedic.getInstance().enablePeriodicTests(this, BluetoothMedic.NO_TEST)
}
  1. You can also use BluetoothMedic without the transmitter test -- generally the scan test is the one that is most important. You can configure this as shown below. But even in this case, you should make sure the user has granted the BLUETOOTH_SCAN permission as well as BACKGROUND_LOCATION if you want the Medic to be able to do its job properly. If these permissions are not granted, you probably don't want to start it because the scan will just fail.
if (ContextCompat.checkSelfPermission(context, Manifest.permission.BACKGROuND_LOCATION) == 
    PackageManager.PERMISSION_GRANTED && 
    ContextCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_SCAN) == 
    PackageManager.PERMISSION_GRANTED) {
  BluetoothMedic.getInstance().enablePeriodicTests(this, BluetoothMedic.SCAN_TEST)`
}
else {
  BluetoothMedic.getInstance().enablePeriodicTests(this, BluetoothMedic.NO_TEST)
}

Of course, the above checks only apply to apps targeting Android 12 and running on Android 12, because the permissions were added. If you allow running on older Android versions, you don't want to check for the BLUETOOTH_SCAN and BLUETOOTH_ADVERTISE permissions if running on an earlier Android version. Sadly, Android permission checks across multiple OS versions have become increasingly complex and requires a lot of code to get things right.

Finally, note that the code above shows disabling the BluetoothMedic if permission is not granted. This is important because the user may grant permission and later revoke it, which could cause a crash. To stop this crash from happening, put the above code (including the else statement that disables BluetoothMedic if no permission is granted) somewhere in the Application.onCreate call chain, as this method is called before any Android scheduled job (including the BluetoothMedic) runs. Doing so will prevent a background crash if BluetoothMedic has been enabled but the required permission is not granted.

For the specific case described by the OP, I suspect this is a race condition. The BluetoothMedic settings are persistent, so if you turn it on, it stays on. It relies on the Android JobScheduler, which will try and run it every ~15 minutes. And it will launch your app if not already running, so if it does so and the permission is denied it will crash. For this reason, you should explicitly disable the BluetoothMedic if the permission is denied. I recommend moving your code that enables/disables BluetoothMedic to the Application.onCreate call chain to stop it before a crash.

Upvotes: 2

Related Questions