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?
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
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:
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" />
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.
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:
requestPermissions()
requestPermissions()
me semble étranger. Un simple appel à requestPermissions()
suffit.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.
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.