Bluetooth LE Scan не работает на Android M в фоновом режиме
Следующий код отлично работает на моем Nexus 9 под управлением Android 5.1.1 (Build LMY48M), но не будет работать на Nexus 9 под управлением 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() {
...
});
В Android 5.x приведенный выше код дает обратный вызов, когда видна реклама производителя, соответствующая фильтру сканирования. (См. Пример вывода Logcat ниже.) На Nexus 9 с MPA44l никаких обратных вызовов не получено. Если вы закомментируете фильтр сканирования, обратные вызовы успешно принимаются на 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}
Кто-нибудь видел, как ScanFilters работают на Android M?
Ответы
Ответ 1
Проблема была не в фильтре сканирования, а в том, что фильтр сканирования использовался только тогда, когда приложение находилось в фоновом режиме. (ОБНОВЛЕНИЕ: Это решение устарело для Android 10, см. ниже.) Начиная с Android M, сканирование в фоновом режиме Bluetooth LE блокируется, если приложение не имеет одно из следующих двух разрешений:
android.permission.ACCESS_COARSE_LOCATION
android.permission.ACCESS_FINE_LOCATION
Приложение, которое я тестировал, не запрашивало ни одно из этих разрешений, поэтому оно не работало в фоновом режиме (единственный раз, когда был активен фильтр сканирования) на Android M. Добавление первого решило проблему.
Я понял, что это была проблема, потому что я увидел следующую строку в 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
Подробности смотрите здесь: https://code.google.com/p/android-developer-preview/issues/detail?id=2964
ОБНОВЛЕНИЕ: Начиная с Android 10, грубое местоположение не достаточно - вам нужно точное местоположение и специальное фоновое разрешение для обнаружения рекламы BLE и маяков в фоновом режиме. Подробнее см. здесь.
Поместите в свой файл AndroidManifest.xml следующее:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.ACCESS_BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Затем добавьте в свою активность следующий код, чтобы динамически запрашивать у пользователя эти разрешения:
private static final int PERMISSION_REQUEST_FINE_LOCATION = 1;
private static final int PERMISSION_REQUEST_BACKGROUND_LOCATION = 2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (this.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
if (this.checkSelfPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
if (this.shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_BACKGROUND_LOCATION)) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("This app needs background location access");
builder.setMessage("Please grant location access so this app can detect beacons in the background.");
builder.setPositiveButton(android.R.string.ok, null);
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
@TargetApi(23)
@Override
public void onDismiss(DialogInterface dialog) {
requestPermissions(new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION},
PERMISSION_REQUEST_BACKGROUND_LOCATION);
}
});
builder.show();
}
else {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Functionality limited");
builder.setMessage("Since background location access has not been granted, this app will not be able to discover beacons in the background. Please go to Settings -> Applications -> Permissions and grant background location access to this app.");
builder.setPositiveButton(android.R.string.ok, null);
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
}
});
builder.show();
}
}
} else {
if (!this.shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) {
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION},
PERMISSION_REQUEST_FINE_LOCATION);
}
else {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Functionality limited");
builder.setMessage("Since location access has not been granted, this app will not be able to discover beacons. Please go to Settings -> Applications -> Permissions and grant location access to this app.");
builder.setPositiveButton(android.R.string.ok, null);
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
}
});
builder.show();
}
}
}
}
Ответ 2
У меня была аналогичная проблема с приложением, подключающимся к bluetooth. Не LE ScanFilter, но это была проблема с разрешениями, как у OP.
Основная причина заключается в том, что, начиная с SDK 23, вы должны запрашивать у пользователя разрешения во время выполнения, используя метод Activity
requestPermissions()
.
Вот что сработало для меня:
-
Добавьте одну из следующих двух строк в AndroidManifest.xml
, внутри корня node:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
-
В своей деятельности перед попыткой подключиться к bluetooth вызовите метод Activity
requestPermissions()
, который откроет системный диалог, чтобы запросить у пользователя разрешение. Диалог разрешений открывается в другом потоке, поэтому не забудьте дождаться результата, прежде чем пытаться подключиться к bluetooth.
-
Заменить Activity
onRequestPermissionsResult()
для обработки результата. Этот метод действительно должен будет что-то сделать, только если пользователь отказался предоставить разрешение, чтобы сообщить пользователю, что приложение не может выполнять активность Bluetooth.
В этом сообщении в блоге есть пример кода, который использует AlertDialogs, чтобы сообщить пользователю, что происходит. Это хорошая отправная точка, но есть некоторые недостатки:
- Он не обрабатывает ожидание окончания потока
requestPermissions()
- AlertDialog, завершающий вызов
requestPermissions()
, кажется мне посторонним. Голый вызов requestPermissions()
достаточно.
Ответ 3
Добавить разрешение местоположения вместе с 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" />
Копировать Вставить этот метод, чтобы запросить и предоставить разрешение на размещение
@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;
}
И затем в onCreate проверьте разрешение
if (Build.VERSION.SDK_INT >= 23) {
// Marshmallow+ Permission APIs
fuckMarshMallow();
}
Надеюсь, это сэкономит ваше время.
Ответ 4
В дополнение к тому, что динозавр прокомментировал на этой странице, у меня были проблемы из-за android:noHistory="true"
в манифесте.
Подробности здесь noHistory = "true"
Кроме того, посмотрите на эту ссылку. Интересно !
Ответ 5
Если ваше приложение предназначено для Android Q, его недостаточно только для грубого расположения, вам нужно использовать точное расположение, в противном случае вы получите эту ошибку:
E/BluetoothUtils: Permission denial: Need ACCESS_FINE_LOCATION permission to get scan results
Смотрите https://developer.android.com/preview/privacy/camera-connectivity#fine-location-telephony-wifi-bt официальный источник.