web-dev-qa-db-fra.com

Bluetooth LE Scan ne fonctionne pas sur Android M en arrière-plan

Le code suivant fonctionne très bien sur mon Nexus 9 exécutant Android 5.1.1 (Build LMY48M), mais ne fonctionnera pas sur un Nexus 9 exécutant Android 6.0 ( Build MPA44l)

List<ScanFilter> filters = new ArrayList<ScanFilter>();
ScanSettings settings = (new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)).build();
ScanFilter.Builder builder = new ScanFilter.Builder();
builder.setManufacturerData((int) 0x0118, new byte[]{(byte) 0xbe, (byte) 0xac}, new byte[]{(byte) 0xff, (byte)0xff});
ScanFilter scanFilter = builder.build();
filters.add(scanFilter);
mBluetoothLeScanner.startScan(filters, settings, new ScanCallback() {
  ...
});

Sur Android 5.x, le code ci-dessus génère un rappel lorsqu'une publicité du fabricant correspondant au filtre de balayage est vue. (Voir l'exemple de sortie Logcat ci-dessous.) Sur le Nexus 9 avec MPA44l, aucun rappel n'est Si vous commentez le filtre d'analyse, les rappels sont reçus avec succès sur le Nexus 9.

09-22 00:07:28.050    1748-1796/org.altbeacon.beaconreference D/BluetoothLeScanner﹕ onScanResult() - ScanResult{mDevice=00:07:80:03:89:8C, mScanRecord=ScanRecord [mAdvertiseFlags=6, mServiceUuids=null, mManufacturerSpecificData={280=[-66, -84, 47, 35, 68, 84, -49, 109, 74, 15, -83, -14, -12, -111, 27, -87, -1, -90, 0, 1, 0, 1, -66, 0]}, mServiceData={}, mTxPowerLevel=-2147483648, mDeviceName=null], mRssi=-64, mTimestampNanos=61272522487278}

Quelqu'un a-t-il vu ScanFilters fonctionner sur Android M?

23
davidgyoung

Le problème n'était pas le filtre d'analyse, mais le fait que le filtre d'analyse n'était utilisé que lorsque l'application était en arrière-plan. À partir de Android M, la numérisation Bluetooth LE en arrière-plan est bloquée, sauf si l'application dispose de l'une des deux autorisations suivantes:

Android.permission.ACCESS_COARSE_LOCATION
Android.permission.ACCESS_FINE_LOCATION

L'application que je testais ne demandait aucune de ces autorisations, donc cela ne fonctionnait pas en arrière-plan (la seule fois où le filtre de scan était actif) sur Android M. L'ajout du premier a résolu le problème.

J'ai réalisé que c'était le problème parce que j'ai vu la ligne suivante dans Logcat:

09-22 22:35:20.152  5158  5254 E BluetoothUtils: Permission denial: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission to get scan results

Voir ici pour plus de détails: https://code.google.com/p/Android-developer-preview/issues/detail?id=2964

19
davidgyoung

J'ai eu un problème similaire avec une application se connectant à Bluetooth. Pas LE ScanFilter, mais c'était un problème d'autorisations tout comme l'OP.

La cause principale est qu'à partir du SDK 23, vous devez demander à l'utilisateur des autorisations lors de l'exécution à l'aide de la méthode requestPermissions() de Activity.

Voici ce qui a fonctionné pour moi:

  1. Ajoutez l'une des deux lignes suivantes à AndroidManifest.xml, À l'intérieur du nœud racine:

    <uses-permission Android:name="Android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission Android:name="Android.permission.ACCESS_COARSE_LOCATION" />
    
  2. Dans votre activité, avant d'essayer de vous connecter à Bluetooth, appelez la méthode requestPermissions() de Activity, qui ouvre une boîte de dialogue système pour demander l'autorisation à l'utilisateur. La boîte de dialogue des autorisations s'ouvre dans un thread différent, alors assurez-vous d'attendre le résultat avant d'essayer de vous connecter à Bluetooth.

  3. Remplacez onRequestPermissionsResult() par Activity pour gérer le résultat. Cette méthode n'aura vraiment besoin de faire quelque chose que si l'utilisateur refuse d'accorder l'autorisation, pour lui dire que l'application ne peut pas faire l'activité Bluetooth.

Ce billet de blog a un exemple de code qui utilise AlertDialogs pour dire à l'utilisateur ce qui se passe. C'est un bon point de départ mais présente quelques lacunes:

  • Il ne gère pas l'attente de la fin du thread requestPermissions()
  • Le AlertDialog encapsulant l'appel à requestPermissions() me semble étranger. Un simple appel à requestPermissions() suffit.
31
dinosaur

Ajouter une autorisation de localisation avec BLE

<uses-permission Android:name="Android.permission.BLUETOOTH" />
<uses-permission Android:name="Android.permission.BLUETOOTH_ADMIN" />
<uses-permission Android:name="Android.permission.ACCESS_FINE_LOCATION" />
<uses-permission Android:name="Android.permission.ACCESS_COARSE_LOCATION" />

Copier Coller cette méthode pour demander et accorder une autorisation de localisation

  @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        switch (requestCode) {
            case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS: {
                Map<String, Integer> perms = new HashMap<String, Integer>();
                // Initial
                perms.put(Manifest.permission.ACCESS_FINE_LOCATION, PackageManager.PERMISSION_GRANTED);


                // Fill with results
                for (int i = 0; i < permissions.length; i++)
                    perms.put(permissions[i], grantResults[i]);

                // Check for ACCESS_FINE_LOCATION
                if (perms.get(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED

                        ) {
                    // All Permissions Granted

                    // Permission Denied
                    Toast.makeText(ScanningActivity.this, "All Permission GRANTED !! Thank You :)", Toast.LENGTH_SHORT)
                            .show();


                } else {
                    // Permission Denied
                    Toast.makeText(ScanningActivity.this, "One or More Permissions are DENIED Exiting App :(", Toast.LENGTH_SHORT)
                            .show();

                    finish();
                }
            }
            break;
            default:
                super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }


    @TargetApi(Build.VERSION_CODES.M)
    private void fuckMarshMallow() {
        List<String> permissionsNeeded = new ArrayList<String>();

        final List<String> permissionsList = new ArrayList<String>();
        if (!addPermission(permissionsList, Manifest.permission.ACCESS_FINE_LOCATION))
            permissionsNeeded.add("Show Location");

        if (permissionsList.size() > 0) {
            if (permissionsNeeded.size() > 0) {

                // Need Rationale
                String message = "App need access to " + permissionsNeeded.get(0);

                for (int i = 1; i < permissionsNeeded.size(); i++)
                    message = message + ", " + permissionsNeeded.get(i);

                showMessageOKCancel(message,
                        new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
                                        REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
                            }
                        });
                return;
            }
            requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
                    REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
            return;
        }

        Toast.makeText(ScanningActivity.this, "No new Permission Required- Launching App .You are Awesome!!", Toast.LENGTH_SHORT)
                .show();
    }

    private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
        new AlertDialog.Builder(ScanningActivity.this)
                .setMessage(message)
                .setPositiveButton("OK", okListener)
                .setNegativeButton("Cancel", null)
                .create()
                .show();
    }

    @TargetApi(Build.VERSION_CODES.M)
    private boolean addPermission(List<String> permissionsList, String permission) {

        if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
            permissionsList.add(permission);
            // Check for Rationale Option
            if (!shouldShowRequestPermissionRationale(permission))
                return false;
        }
        return true;
    }

Et puis dans onCreate vérifier l'autorisation

 if (Build.VERSION.SDK_INT >= 23) {
            // Marshmallow+ Permission APIs
            fuckMarshMallow();
        }

J'espère que cela vous fera gagner du temps.

10
Hitesh Sahu

Si votre application cible Android Q, ce n'est pas suffisant avec seulement un emplacement grossier, vous devez utiliser un emplacement fin, sinon vous obtiendrez cette erreur:

E/BluetoothUtils: Permission denial: Need ACCESS_FINE_LOCATION permission to get scan results

Voir https://developer.Android.com/preview/privacy/camera-connectivity#fine-location-telephony-wifi-bt pour la source officielle.

0
Emil