flutter_blue icon indicating copy to clipboard operation
flutter_blue copied to clipboard

Support new Android 12 Bluetooth permissions

Open navaronbracke opened this issue 4 years ago • 54 comments

Android 12 introduces new Bluetooth permissions: BLUETOOTH_SCAN & BLUETOOTH_CONNECT https://developer.android.com/about/versions/12/features/bluetooth-permissions

The plugin should update its manifest.xml to match the new format outlined in the documentation. The documentation in the README should reflect this change.

It should also update its permission handling. On Android 11 and lower: Use the current permission request logic. On Android 12+: Use permission logic that matches the required behavior:

When calling startScan() we should request permission for BLUETOOTH_SCAN, prior to actually starting the scan. When calling device.connect() we should request permission for BLUETOOTH_CONNECT.

navaronbracke avatar Jun 07 '21 09:06 navaronbracke

Strong agree!

dolan23 avatar Jul 15 '21 13:07 dolan23

My own flutter_blue fork that supports Android 12 permission: https://github.com/espresso3389/flutter_blue

It's still under development but the example code seems work correctly. I'm preparing PR but the changes are relatively large and I'm not sure it can be safely merged or not...

espresso3389 avatar Sep 19 '21 08:09 espresso3389

Could you fork off of your repo from this https://github.com/boskokg/flutter_blue/ repo or make a PR against it? That repo has many more PRs https://github.com/pauldemarco/flutter_blue/issues/929#issuecomment-930759129

MrCsabaToth avatar Dec 24 '21 22:12 MrCsabaToth

Android 12 support is badly needed.

MrCsabaToth avatar Dec 24 '21 22:12 MrCsabaToth

Sorry, look like the fork I mentioned already has 12 support.

MrCsabaToth avatar Dec 24 '21 22:12 MrCsabaToth

How do I use the espresso3389 fork in my Flutter project instead of the pauldemarco one?

tricaricom avatar Jan 10 '22 02:01 tricaricom

@tricaricom Do like the following on your pubspec.yaml:

  flutter_blue:
    git: https://github.com/espresso3389/flutter_blue

espresso3389 avatar Jan 10 '22 02:01 espresso3389

Thanks @espresso3389 .

So when I use your flutter_blue, I get this error:

E/AndroidRuntime(15548): java.lang.SecurityException: Need android.permission.BLUETOOTH_CONNECT permission for android.content.AttributionSource@41052d27: AdapterService getRemoteName

However, I have these permissions in the manifest:

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

This is interesting because I explicitly added BLUETOOTH_CONNECT here hoping to overcome this error. But when I look in your README, you do not include this permission. Very confusing....

If it makes any difference, I am testing on a Google Pixel 6 running Android 12.

tricaricom avatar Jan 10 '22 02:01 tricaricom

@tricaricom Same issue is reported here but I could not reproduce the issue with flutter_blue sample and my own codes...

Can you provide me with the complete sample that causes the issue?

espresso3389 avatar Jan 10 '22 10:01 espresso3389

@espresso3389 what repo/branch should I use for testing?

ekuleshov avatar Jan 10 '22 13:01 ekuleshov

@ekuleshov I'm working on "my" master branch, so the following lines are enough:

  flutter_blue:
    git: https://github.com/espresso3389/flutter_blue

espresso3389 avatar Jan 10 '22 13:01 espresso3389

@espresso3389 not sure if it is related, for my app I have compileSdkVersion 31 and targetSdkVersion 30 and I'm running on Pixel 3xl. For some reason I also can't reproduce it with the example app, but it is failing consistently with my own app.

When running on Android 12, the system settings show only one permission "nearby devices" even so on the API side there are two separate permissions SCAN and CONNECT. It seems like manually granting nearby permission in the system settings grants both SCAN and CONNECT ones on my device.

All in all it seems like both SCAN and CONNECT permissions need to be requested and granted when calling BLE scan. E.g. see https://xizzhu.me/post/2021-10-05-android-12-bluetooth-permissions/

The CONNECT error is thrown from the ProtoMaker.from(result.getDevice(), result) from inside getScanCallback21():

    static Protos.BluetoothDevice from(BluetoothDevice device) {
        Protos.BluetoothDevice.Builder p = Protos.BluetoothDevice.newBuilder();
        p.setRemoteId(device.getAddress());
        String name = device.getName(); <--- THIS ONE REQUIRES connect permission
        ...

Note that BluetoothDevice.getName() is declared like this:

    @RequiresLegacyBluetoothPermission
    @RequiresBluetoothConnectPermission
    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
    public String getName() {
      ...

Also, perhaps to avoid the app crash the code ingetScanCallback21() method need to be wrapped with a try/catch to avoid fatal crashes for an entire app.

ekuleshov avatar Jan 10 '22 16:01 ekuleshov

@ekuleshov The plugin (on the fork) basically requires both compileSdkVersion and targetSdkVersion set to 31:

https://github.com/espresso3389/flutter_blue/blob/80fca568e59428722490a8cae3c56a039c4dc283/android/build.gradle#L31

And, the example also does the same.

I'm not sure what happens if you set targetSdkVersion 30 on your code...

espresso3389 avatar Jan 10 '22 16:01 espresso3389

I'm not sure what happens if you set targetSdkVersion 30 on your code...

I already tried to bump the targetSdkVersion to 31 in my app, but still getting the same error for CONNECT permission.

If I go to the system settings, remove nearby permission and then grant it again the scan in my app is working after that. So, I still think both permissions need to be requested for the scan. Note the annotation on the BluetoothDevice.getName() which is called by scan.

ekuleshov avatar Jan 10 '22 16:01 ekuleshov

@ekuleshov

I still think both permissions need to be requested for the scan. Note the annotation on the BluetoothDevice.getName() which is called by scan.

Ah, I see your point. I'll update the permission logic to deal with the permissions.

espresso3389 avatar Jan 10 '22 16:01 espresso3389

And, the example also does the same.

BTW, when you do a fresh install for the example app and decline Bluetooth permission requested on the first BLE scan attempt the app gets a null pointer dereference error when getting the snapshot.data! value. It seems like it need to check and handle the snapshot.error before unconditionally retrieving snapshot.data!.

StreamBuilder<List<BluetoothDevice>>(
                stream: Stream.periodic(Duration(seconds: 2))
                    .asyncMap((_) => FlutterBlue.instance.connectedDevices),
                initialData: [],
                builder: (c, snapshot) => Column(
                  children: snapshot.data! <-- HERE
                      .map((d) => ListTile( ...

ekuleshov avatar Jan 10 '22 17:01 ekuleshov

It'd be so great if https://github.com/espresso3389/flutter_blue/ would contribute to https://github.com/boskokg/flutter_blue/ to unify efforts. boskokg also has null safety and permissions but a whole bunch more PRs.

MrCsabaToth avatar Jan 10 '22 17:01 MrCsabaToth

@MrCsabaToth Yes, it's good if he can do so easily. There seems at least two problems:

  1. He seems so busy
  2. SDK 31 breaks so many things; some of them need changes on other Flutter plugins

Anyway, PR #940 is for the issue.

espresso3389 avatar Jan 10 '22 18:01 espresso3389

@ekuleshov Try the latest commit (f0d9cb), I've changed the way to request/check permissions on scan. (I actually could not reproduce the issue but I think it handles the situations better).

espresso3389 avatar Jan 10 '22 18:01 espresso3389

@ekuleshov Try the latest commit (f0d9cb), I've changed the way to request/check permissions on scan. (I actually could not reproduce the issue but I think it handles the situations better).

Somehow it got worse. No permission request on a fresh install and I get this error now when scan() is called:

E/flutter ( 5232): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: PlatformException(startScan, Need android.permission.BLUETOOTH_SCAN permission for android.content.AttributionSource@7c328bd: GattService registerScanner, java.lang.SecurityException: Need android.permission.BLUETOOTH_SCAN permission for android.content.AttributionSource@7c328bd: GattService registerScanner
E/flutter ( 5232): 	at android.os.Parcel.createExceptionOrNull(Parcel.java:2425)
E/flutter ( 5232): 	at android.os.Parcel.createException(Parcel.java:2409)
E/flutter ( 5232): 	at android.os.Parcel.readException(Parcel.java:2392)
E/flutter ( 5232): 	at android.os.Parcel.readException(Parcel.java:2334)
E/flutter ( 5232): 	at android.bluetooth.IBluetoothGatt$Stub$Proxy.registerScanner(IBluetoothGatt.java:1727)
E/flutter ( 5232): 	at android.bluetooth.le.BluetoothLeScanner$BleScanCallbackWrapper.startRegistration(BluetoothLeScanner.java:426)
E/flutter ( 5232): 	at android.bluetooth.le.BluetoothLeScanner.startScan(BluetoothLeScanner.java:278)
E/flutter ( 5232): 	at android.bluetooth.le.BluetoothLeScanner.startScan(BluetoothLeScanner.java:154)
E/flutter ( 5232): 	at com.pauldemarco.flutter_blue.FlutterBluePlugin.startScan21(FlutterBluePlugin.java:846)
E/flutter ( 5232): 	at com.pauldemarco.flutter_blue.FlutterBluePlugin.startScan(FlutterBluePlugin.java:782)
E/flutter ( 5232): 	at com.pauldemarco.flutter_blue.FlutterBluePlugin.lambda$onMethodCall$0$FlutterBluePlugin(FlutterBluePlugin.java:246)
E/flutter ( 5232): 	at com.pauldemarco.flutter_blue.-$$Lambda$FlutterBluePlugin$npO5ZnMb5Xa1WrWJ1kqQqzqyO7E.op(Unknown Source:6)
E/flutter ( 5232): 	at com.pauldemarco.flutter_blue.FlutterBluePlugin.ensurePermissionsBeforeAction(FlutterBluePlugin.java:666)
E/flutter ( 5232): 	at com.pauldemarco.flutter_blue.FlutterBluePlugin.onMethodCall(FlutterBluePlugin.java:244)
E/flutter ( 5232): 	at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:262)
E/flutter ( 5232): 	at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:178)
E/flutter ( 5232): 	at io.flutter.embedding.engine.dart.DartMessenger.lambda$handleMessageFromDart$0$DartMessenger(DartMessenger.java:206)
E/flutter ( 5232): 	at io.flutter.embedding.engine.dart.-$$Lambda$DartMessenger$6ZD1MYkhaLxyPjtoFDxe45u43DI.run(Unknown Source:12)

ekuleshov avatar Jan 10 '22 19:01 ekuleshov

@ekuleshov Sorry, the condition is completely reversed... Please try 256f1a.

espresso3389 avatar Jan 10 '22 19:01 espresso3389

@ekuleshov Sorry, the condition is completely reversed... Please try 256f1a.

Success! Thank you.

ekuleshov avatar Jan 10 '22 19:01 ekuleshov

The @espresso3389 fork does not work properly on BOTH pre-Android 12 and post-Android 12 devices. I can get this to work fine on my Android 12 device. But when I test it on Android 9, it does discover the bluetooth device, but when I try to connect, I get this error:

E/MethodChannel#plugins.pauldemarco.com/flutter_blue/methods(18144): Failed to handle method call E/MethodChannel#plugins.pauldemarco.com/flutter_blue/methods(18144): java.lang.NullPointerException: Attempt to get length of null array

E/flutter (18144): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: PlatformException(error, Attempt to get length of null array, null, java.lang.NullPointerException: Attempt to get length of null array E/flutter (18144): at androidx.core.app.ActivityCompat.requestPermissions(ActivityCompat.java:504) E/flutter (18144): at com.pauldemarco.flutter_blue.FlutterBluePlugin.ensurePermissionsBeforeAction(FlutterBluePlugin.java:660) E/flutter (18144): at com.pauldemarco.flutter_blue.FlutterBluePlugin.ensurePermissionBeforeAction(FlutterBluePlugin.java:650) E/flutter (18144): at com.pauldemarco.flutter_blue.FlutterBluePlugin.onMethodCall(FlutterBluePlugin.java:282) E/flutter (18144): at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:262) E/flutter (18144): at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:178) E/flutter (18144): at io.flutter.embedding.engine.dart.DartMessenger.lambda$handleMessageFromDart$0$DartMessenger(DartMessenger.java:206) E/flutter (18144): at io.flutter.embedding.engine.dart.-$$Lambda$DartMessenger$6ZD1MYkhaLxyPjtoFDxe45u43DI.run(Unknown Source:12) E/flutter (18144): at android.os.Handler.handleCallback(Handler.java:873) E/flutter (18144): at android.os.Handler.dispatchMessage(Handler.java:99) E/flutter (18144): at android.os.Looper.loop(Looper.java:215) E/flutter (18144): at android.app.ActivityThread.main(ActivityThread.java:6939) E/flutter (18144): at java.lang.reflect.Method.invoke(Native Method) E/flutter (18144): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) E/flutter (18144): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:870) E/flutter (18144): ) E/flutter (18144): #0 StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:607:7) E/flutter (18144): #1 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:167:18) E/flutter (18144): E/flutter (18144): #2 BluetoothDevice.connect (package:flutter_blue/src/bluetooth_device.dart:37:5) E/flutter (18144):

tricaricom avatar Jan 17 '22 21:01 tricaricom

It seems like it needs some better null handling. It passes null to allPermissionsGranted() which returns false and then it calls ActivityCompat.requestPermissions() on the same array. Either it should not use null at the line 654 (return an empty String[] instead) or either return true from the allPermissionsGranted() when argument is null.

ekuleshov avatar Jan 18 '22 02:01 ekuleshov

@ekuleshov Now allPermissionsGranted returns null for ~~permissions==null~~ permissions==false case.

espresso3389 avatar Jan 18 '22 06:01 espresso3389

@ekuleshov Now allPermissionsGranted returns null for permissions==null case.

That works, thank you! Though from the error-proneness point of view it would be good to avoid using nulls values for anything. But that is also somewhat matter of a code style.

ekuleshov avatar Jan 18 '22 14:01 ekuleshov

@ekuleshov

it would be good to avoid using nulls values for anything.

Yes, I don't want to use Java, null,... It's not originally my code :(

espresso3389 avatar Jan 18 '22 17:01 espresso3389

@MrCsabaToth Yes, it's good if he can do so easily. There seems at least two problems:

1. He seems so busy

2. SDK 31 breaks so many things; some of them need changes on other Flutter plugins

Anyway, PR #940 is for the issue.

What I thought is it'd be so awesome if your fork was forked from boskokg (https://github.com/boskokg/flutter_blue/ - which is a fork of pauldemarco) instead of directly from pauldemarco, because boskokg has a lot of PR merged.

MrCsabaToth avatar Jan 24 '22 03:01 MrCsabaToth

@MrCsabaToth I've created a PR (boskokg/flutter_blue#29) on boskokg's fork.

espresso3389 avatar Jan 24 '22 17:01 espresso3389

@espresso3389 thank you. tomorrow I will check and merge it and increase the version.

boskokg avatar Jan 24 '22 21:01 boskokg