Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android: Permission Missing #1542

Open
brinata opened this issue Apr 22, 2024 · 8 comments
Open

Android: Permission Missing #1542

brinata opened this issue Apr 22, 2024 · 8 comments
Labels
Backend: Android bug Something isn't working

Comments

@brinata
Copy link

brinata commented Apr 22, 2024

  • bleak version: Latest ( 0.21.0)
  • Python version: 3.11
  • Operating System: Ubuntu
  • android version: API 33

Trying to run the basic example given for android
https://github.com/hbldh/bleak/tree/develop/examples/kivy

Added Java files from https://github.com/hbldh/bleak/tree/develop/bleak/backends/p4android/java/com/github/hbldh/bleak to local folder java and in buildozer.spec modifed android.add_src = java

log:

04-22 10:53:06.613 12555 12647 I python : [INFO ] [example ]scanning
04-22 10:53:06.614 12555 12647 I python : [DEBUG ] Starting BTLE scan
04-22 10:53:06.710 12555 12647 I python : [INFO ] [example ]ERROR User denied access to ['android.permission.ACCESS_FINE_LOCATION', 'android.permission.ACCESS_COARSE_LOCATION', 'android.permission.ACCESS_BACKGROUND_LOCATION']
04-22 10:53:06.710 12555 12647 I python : [ERROR ] [Exception in callback None()
04-22 10:53:06.710 12555 12647 I python : handle]
04-22 10:53:06.710 12555 12647 I python : Traceback (most recent call last):
04-22 10:53:06.710 12555 12647 I python : File "/home/ronni/progetti/app_bleak/.buildozer/android/platform/build-arm64-v8a/build/other_builds/python3/arm64-v8a__ndk_target_21/python3/Lib/asyncio/events.py", line 80, in _run
04-22 10:53:06.710 12555 12647 I python : TypeError: 'NoneType' object is not callable

@brinata
Copy link
Author

brinata commented Apr 22, 2024

After some tests, the permission request function implemented inside the scanner for android is not compatibile with the api version = 33, most probably because the p4a is changed.

the api version 33 doesn't like the request ACCESS_BACKGROUND_LOCATION.

after removing it for API version >= 30, the scanner is working, but the client give out the followind error:

    if not descriptor:
        raise BleakError(f"Descriptor {desc_specifier} was not found!")

after connecting, when calling :

await client.start_notify(UART_TX_CHAR_UUID, self.handle_rx)

@dlech dlech added bug Something isn't working Backend: Android labels Apr 22, 2024
@brinata
Copy link
Author

brinata commented Apr 22, 2024

Please find the logs:

04-22 18:07:23.649 26795 26958 I python : Start Scanning
04-22 18:07:23.968 26795 26958 I python : [DEBUG ] Starting BTLE scan
04-22 18:07:25.110 26795 26795 I python : [True, True]
04-22 18:07:27.559 26795 26795 I python : [True, True, True, True]
04-22 18:07:27.609 26795 26958 I python : [DEBUG ] Waiting for android api onScan
04-22 18:07:27.710 26795 26958 I python : [DEBUG ] Java state transfer onScan error=None data=(<android.bluetooth.le.ScanResult at 0x7434cafcb0 jclass=android/bluetooth/le/ScanResult jself=<LocalRef obj=0xab0e at 0x7434cab090>>,)
04-22 18:07:27.710 26795 26958 I python : [DEBUG ] onScan succeeded (<android.bluetooth.le.ScanResult at 0x7434cafcb0 jclass=android/bluetooth/le/ScanResult jself=<LocalRef obj=0xab0e at 0x7434cab090>>,)
04-22 18:07:27.826 26795 26958 I python : Nulla !!!
04-22 18:07:27.875 26795 26958 I python : trovato!
04-22 18:07:27.875 26795 26958 I python : [DEBUG ] Stopping BTLE scan
04-22 18:07:27.881 26795 26958 I python : Device found
04-22 18:07:27.884 26795 26958 I python : Attesa connessione
04-22 18:07:27.887 26795 26958 I python : [DEBUG ] [Connecting to BLE device @ BB]3D:7A:F6:4F:78
04-22 18:07:27.888 26795 26958 I python : [DEBUG ] Waiting for android api onConnectionStateChange
04-22 18:07:28.588 26795 26958 I python : [DEBUG ] Java state transfer onConnectionStateChange error=None data=(2,)
04-22 18:07:28.588 26795 26958 I python : [DEBUG ] onConnectionStateChange succeeded ()
04-22 18:07:28.589 26795 26958 I python : [DEBUG ] Connection successful.
04-22 18:07:28.590 26795 26958 I python : [DEBUG ] requesting mtu...
04-22 18:07:28.590 26795 26958 I python : [DEBUG ] Waiting for android api onMtuChanged
04-22 18:07:29.316 26795 26958 I python : [DEBUG ] Java state transfer onMtuChanged error=None data=(247,)
04-22 18:07:29.318 26795 26958 I python : [DEBUG ] onMtuChanged succeeded (247,)
04-22 18:07:29.318 26795 26958 I python : [DEBUG ] discovering services...
04-22 18:07:29.319 26795 26958 I python : [DEBUG ] Waiting for android api onServicesDiscovered
04-22 18:07:29.338 26795 26958 I python : [DEBUG ] Java state transfer onServicesDiscovered error=None data=()
04-22 18:07:29.340 26795 26958 I python : [DEBUG ] onServicesDiscovered succeeded ()
04-22 18:07:29.341 26795 26958 I python : [DEBUG ] Get Services...
04-22 18:07:29.371 26795 26958 I python : Connected, start typing and press ENTER...
04-22 18:07:29.375 26795 26958 I python : ERROR Descriptor None was not found!
04-22 18:07:29.400 26795 26958 I python : ERROR Descriptor None was not found!
04-22 18:07:29.415 26795 26958 I python : [DEBUG ] BTLE scan already stopped

@brinata
Copy link
Author

brinata commented Apr 22, 2024

and the implemented python code:

async def ble_service(self):

    await asyncio.sleep(1)
    print("Start Scanning")
    device = None

    while True:
        if self.scanning :
            try:
                #cancello il device se già esistente
                if device is not None:
                    del device

                device = await BleakScanner.find_device_by_filter(self.match_nus_uuid)

                if device is None:
                    print("Device not present")

                else:
                    print("Device found")
                    try:
                        client = BleakClient(device, disconnected_callback=self.handle_disconnect, timeout=5)
                        print("Attesa connessione")
                        self.connected = await client.connect()
                        if self.connected is True:
                            self.topbar_status_color = 'green'
                            print("Connected, start typing and press ENTER...")
                            await client.start_notify(UART_TX_CHAR_UUID, self.handle_rx)   
                            # ottengo il servizio di interesse
                            print("uno")
                            nus = client.services.get_service(UART_SERVICE_UUID)
                            # ottengo la caratteristica di interesse
                            rx_char = nus.get_characteristic(UART_RX_CHAR_UUID)
                            print("due")

                            await asyncio.sleep(0.5)
                            self.invia_comando(BLE.BLE_ACT_READ)
        #                    Clock.schedule_once(self.invia_comando, 0.5)

                            print("entro nel loop")
                            while True:
                                await asyncio.sleep(0.25)

                                if self.end_app is True:
                                    return

                                if self.connected is False:
        #                        if client.is_connected() is False:
                                    print("dispositivo disconnesso, esco dal loop")
                                    while not self.tx_queue.empty():
                                        # Depending on your program, you may want to
                                        # catch QueueEmpty
                                        self.tx_queue.get_nowait()
                                        self.tx_queue.task_done()
                                    await client.disconnect()
                                    break

                                if self.tx_queue.empty() is False:
                                    data = self.tx_queue.get_nowait()

                                    print(data)


                                    data = bytes([STX]) + data + bytes([ETX])

                                    minimo = min(len(data), 20, rx_char.max_write_without_response_size)
                                            

                                    self.ms = time.time()    

                                    for s in sliced(data, minimo):
                                        await client.write_gatt_char(rx_char, s)

                    
                                print("sent:", data)                        
                    except BleakError as e:
                        print(f"ERROR {e}")
                        Clock.schedule_once(lambda dt: self.scanning_dialog(f"ERROR {e}"), 0.05)
                        await asyncio.sleep(2)


            except BleakError as e:

                print(f"ERROR {e}")
                Clock.schedule_once(lambda dt: self.scanning_dialog(f"ERROR {e}"), 0.05)
                await asyncio.sleep(2)

            except jnius.jnius.JavaException as e:
                print(f"ERROR {e}")
                Clock.schedule_once(lambda dt: self.scanning_dialog(f"ERROR {e}"), 0.05)
                await asyncio.sleep(2)


        else:
            await asyncio.sleep(2)

@dlech
Copy link
Collaborator

dlech commented Apr 22, 2024

Permission issue is possible duplicate of #1363.

Descriptor issues is possible duplicate of/related to #883

@brinata
Copy link
Author

brinata commented Apr 22, 2024

the BLE define:

UART_SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
UART_RX_CHAR_UUID = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
UART_TX_CHAR_UUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"

the print result inside the start_notify:

print(characteristic) --> 6e400003-b5a3-f393-e0a9-e50e24dcca9e (Handle: 14): Nordic UART TX

@brinata
Copy link
Author

brinata commented Apr 22, 2024

before calling start_noify:

print(client.services.characteristics) -->

04-22 18:44:38.587 32277 32401 I python : {3: <bleak.backends.p4android.characteristic.BleakGATTCharacteristicP4Android object at 0x7434bf7010>, 5: <bleak.backends.p4android.characteristic.BleakGATTCharacteristicP4Android object at 0x7434bf7050>, 8: <bleak.backends.p4android.characteristic.BleakGATTCharacteristicP4Android object at 0x7434bbbb10>, 12: <bleak.backends.p4android.characteristic.BleakGATTCharacteristicP4Android object at 0x7434bbbad0>, 14: <bleak.backends.p4android.characteristic.BleakGATTCharacteristicP4Android object at 0x7434bb9250>}

@brinata
Copy link
Author

brinata commented Apr 22, 2024

Can you suggest any workaround ?

@brinata
Copy link
Author

brinata commented Apr 23, 2024

Descriptor issue:

After some test i discovered that the issue is in the add_descriptor function ( \bleak\backends\p4android\characteristic.py)

i added some debug print and modification as follows:

    def add_descriptor(self, descriptor: BleakGATTDescriptor):
        """Add a :py:class:`~BleakGATTDescriptor` to the characteristic.

        Should not be used by end user, but rather by `bleak` itself.
        """
        self.__descriptors.append(descriptor)
        print("descriptor.uuid...", descriptor.uuid)
        print("defs.CLIENT_CHARACTERISTIC_CONFIGURATION_UUID...", defs.CLIENT_CHARACTERISTIC_CONFIGURATION_UUID)
#        if descriptor.uuid == defs.CLIENT_CHARACTERISTIC_CONFIGURATION_UUID:
        if defs.CLIENT_CHARACTERISTIC_CONFIGURATION_UUID in descriptor.uuid:
            self.__notification_descriptor = descriptor

        print(self.__notification_descriptor)

it works.

the problem is that the fileds "descriptor.uuid" and "defs.CLIENT_CHARACTERISTIC_CONFIGURATION_UUID" are different, so the operation "self.__notification_descriptor = descriptor" never happens

Permission issue:

i modified the "start" function of the scanner as follow:

    async def start(self) -> None:
        if BleakScannerP4Android.__scanner is not None:
            raise BleakError("A BleakScanner is already scanning on this adapter.")

        logger.debug("Starting BTLE scan")

        loop = asyncio.get_running_loop()

        if self.__javascanner is None:
            if self.__callback is None:
                self.__callback = _PythonScanCallback(self, loop)

            permission_acknowledged = loop.create_future()

            def handle_permissions(permissions, grantResults):
                if any(grantResults):
                    print(grantResults)
                    loop.call_soon_threadsafe(
                        permission_acknowledged.set_result, grantResults
                    )
                else:
                    loop.call_soon_threadsafe(
                        permission_acknowledged.set_exception(
                            BleakError("User denied access to " + str(permissions))
                        )
                    )


            request_permissions(
                [
                    Permission.ACCESS_FINE_LOCATION,
                    Permission.ACCESS_COARSE_LOCATION
#                    "android.permission.ACCESS_BACKGROUND_LOCATION",
                ],
                handle_permissions,
            )
            await permission_acknowledged



            VERSION = autoclass('android.os.Build$VERSION')

            if VERSION.SDK_INT < 30:
                permission_acknowledged.cancel()
                permission_acknowledged = loop.create_future()
                request_permissions(
                    [
    #                    Permission.ACCESS_FINE_LOCATION,
    #                    Permission.ACCESS_COARSE_LOCATION,
                        "android.permission.ACCESS_BACKGROUND_LOCATION"
                    ],
                    handle_permissions,
                )
                await permission_acknowledged



            permission_acknowledged.cancel()
            permission_acknowledged = loop.create_future()

            request_permissions(
                [
                        Permission.BLUETOOTH_SCAN,
                        Permission.BLUETOOTH_CONNECT,
                        Permission.BLUETOOTH_ADMIN,
                        Permission.BLUETOOTH
                ],
                handle_permissions,
            )
            await permission_acknowledged





            self.__adapter = defs.BluetoothAdapter.getDefaultAdapter()
            if self.__adapter is None:
                raise BleakError("Bluetooth is not supported on this hardware platform")
            if self.__adapter.getState() != defs.BluetoothAdapter.STATE_ON:
                raise BleakError("Bluetooth is not turned on")

            self.__javascanner = self.__adapter.getBluetoothLeScanner()

        BleakScannerP4Android.__scanner = self

        filters = cast("java.util.List", defs.List())
        if self._service_uuids:
            for uuid in self._service_uuids:
                filters.add(
                    defs.ScanFilterBuilder()
                    .setServiceUuid(defs.ParcelUuid.fromString(uuid))
                    .build()
                )

        scanfuture = self.__callback.perform_and_wait(
            dispatchApi=self.__javascanner.startScan,
            dispatchParams=(
                filters,
                defs.ScanSettingsBuilder()
                .setScanMode(self.__scan_mode)
                .setReportDelay(0)
                .setPhy(defs.ScanSettings.PHY_LE_ALL_SUPPORTED)
                .setNumOfMatches(defs.ScanSettings.MATCH_NUM_MAX_ADVERTISEMENT)
                .setMatchMode(defs.ScanSettings.MATCH_MODE_AGGRESSIVE)
                .setCallbackType(defs.ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
                .build(),
                self.__callback.java,
            ),
            resultApi="onScan",
            return_indicates_status=False,
        )
        self.__javascanner.flushPendingScanResults(self.__callback.java)

        try:
            async with async_timeout(0.2):
                await scanfuture
        except asyncio.exceptions.TimeoutError:
            pass
        except BleakError as bleakerror:
            await self.stop()
            if bleakerror.args != (
                "onScan",
                "SCAN_FAILED_APPLICATION_REGISTRATION_FAILED",
            ):
                raise bleakerror
            else:
                # there might be a clearer solution to this if android source and vendor
                # documentation are reviewed for the meaning of the error
                # https://stackoverflow.com/questions/27516399/solution-for-ble-scans-scan-failed-application-registration-failed
                warnings.warn(
                    "BT API gave SCAN_FAILED_APPLICATION_REGISTRATION_FAILED.  Resetting adapter."
                )

                def handlerWaitingForState(state, stateFuture):
                    def handleAdapterStateChanged(context, intent):
                        adapter_state = intent.getIntExtra(
                            defs.BluetoothAdapter.EXTRA_STATE,
                            defs.BluetoothAdapter.STATE_ERROR,
                        )
                        if adapter_state == defs.BluetoothAdapter.STATE_ERROR:
                            loop.call_soon_threadsafe(
                                stateOffFuture.set_exception,
                                BleakError(f"Unexpected adapter state {adapter_state}"),
                            )
                        elif adapter_state == state:
                            loop.call_soon_threadsafe(
                                stateFuture.set_result, adapter_state
                            )

                    return handleAdapterStateChanged

                logger.info(
                    "disabling bluetooth adapter to handle SCAN_FAILED_APPLICATION_REGSTRATION_FAILED ..."
                )
                stateOffFuture = loop.create_future()
                receiver = BroadcastReceiver(
                    handlerWaitingForState(
                        defs.BluetoothAdapter.STATE_OFF, stateOffFuture
                    ),
                    actions=[defs.BluetoothAdapter.ACTION_STATE_CHANGED],
                )
                receiver.start()
                try:
                    self.__adapter.disable()
                    await stateOffFuture
                finally:
                    receiver.stop()

                logger.info("re-enabling bluetooth adapter ...")
                stateOnFuture = loop.create_future()
                receiver = BroadcastReceiver(
                    handlerWaitingForState(
                        defs.BluetoothAdapter.STATE_ON, stateOnFuture
                    ),
                    actions=[defs.BluetoothAdapter.ACTION_STATE_CHANGED],
                )
                receiver.start()
                try:
                    self.__adapter.enable()
                    await stateOnFuture
                finally:
                    receiver.stop()
                logger.debug("restarting scan ...")

                return await self.start()

it seems it works. the issue is in the api version : if you ask for all the postion permissions simultaneously the OS always replys immediatly with a negative answare. If yoy ask for the ACCESS_BACKGROUND_LOCATION later it works

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Backend: Android bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants