Разработчики Вы там совсем обнаглели ? Зачем Вам мое местоположение?
Типовой отзыв для андроид приложения, работающего с блютуз устройством.
Статья построена в виде спора с воображаемым пользователем. Для андроид разработчиков рассмотрены как классические способы, так и рекомендумые альтернативы.
От вашей программы требуется только отправить данные на устройство!
Минимально необходимый код (далее МНК) для отправки данных на классическое блютуз устройство с реализаций SPP (Serial Port Protocol)
UUID myUUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
BluetoothAdapter defaultAdapter = BluetoothAdapter.getDefaultAdapter();
BluetoothDevice remoteDevice = defaultAdapter.getRemoteDevice("DC:0D:30:8A:AD:C8");
BluetoothSocket socketToServiceRecord = remoteDevice.createRfcommSocketToServiceRecord(myUUID);
socketToServiceRecord.connect();
DataOutputStream dataOutputStream = new DataOutputStream(socketToServiceRecord.getOutputStream());
dataOutputStream.write("Hello mir!\n\n".getBytes(StandardCharsets.UTF_8));
dataOutputStream.flush();
Thread.sleep(1000);
dataOutputStream.close();
socketToServiceRecord.close();
Если мы запустим МНК, то получим:
java.lang.SecurityException: Need BLUETOOTH permission: Neither user 10632 nor current process has android.permission.BLUETOOTH.
Необходимо добавить для работоспособности этого кода в манифесте приложения:
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
До апи 31 существовали только 2 разрешения:
android.permission.BLUETOOTH и android.permission.BLUETOOTH_ADMIN
В андроид 12 переработали набор пермишенов. Ознакомиться подробнее можно по ссылке.
Главное отличие новых разрешений в том, что их нужно не только добавить в манифест, но и запросить у пользователя явно.
Вот. Сами пишете, что доступ к местоположению не нужен.
Обратите внимание на следующую строку
defaultAdapter.getRemoteDevice("DC:0D:30:8A:AD:C8");
Видите строку с двоеточиями ? Это mac адрес устройства. У каждого устройства он должен быть своим и уникальным. Вы знаете адрес например своего принтера ? Устроит ли вас просто поле ввода для этого значения ?
На моем принтере есть наклейка с QR. Считайте ее.
Это исключения, кроме того нет единого формата для кодирования информации о параметрах подключения.
Я буду сам сопрягать устройства через системные настройки. Дайте мне выбрать нужное из них.
Я могу помочь Вам попасть сразу в нужное место системных настроек:
Intent intentOpenBluetoothSettings = new Intent();
intentOpenBluetoothSettings.setAction(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS);
startActivity(intentOpenBluetoothSettings);
и вам не придется долго добираться до него.
Получить список сопряженных:
Set<BluetoothDevice> btDevices = mBtAdapter.getBondedDevices();
Казалось бы одна строчка кода, что тут может не работать?
Во первых, всё таки встречаются устройства без BT. Подстраховаться можно строкой в манифесте.
<uses-feature android:name="android.hardware.bluetooth" android:required="true" />
В этом случае приложение нельзя будет поставить на андроид устройство без блютуз адаптера. Если с перефирийным устройством можно общаться еще через USB или сеть, то меняем на required="false".
Если адаптера нет, то BluetoothAdapter.getDefaultAdapter() вернет null.
Попутно пожалуюсь. Ну зачем ее сделали депрекайтед? Альтернатива ужасно не удобная. Теперь еще контекст в фоновые потоки протаскивать для получения адаптера или сам адаптер. А за столько лет существования андроида так и не сделали, чтобы два и более адаптера поддерживалось одновременно. А еще проблем добавляют .
Во вторых, опять головная боль в 12м андроиде. Нужно учесть, что пермишен BLUETOOTH_CONNECT предоставлен.
Ворчание. Раньше было проще. Автоматом давался по факту наличия в манифесте. Теперь придется аналогично критичными. А еще нельзя попросить один раз и запомнить, что получил. Механизм автоматического отзыва у неиспользуемых приложений появился. Так что здравствуй куча проверок начиная с того, на какой версии андроида запущено.
Совет вместо проверок, лучше обернуть SecurityException в кастомное исключение и обработать его там, где есть возможность позвать запрос на предоставление разрещения иначе там получается большая лапша проверок начиная с того, что версия андроида 12 и выше и далее а дано ли разрешение.
В третьих, getBondedDevices() вернет null при выключенном адаптере.
Действия с получением списка сопряженных и их обработкой вынесем в функцию getBonded() . Вместо тривиального уведомления "Включите" реализуем включение.
if (!mBtAdapter.isEnabled()) {
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
btActivityResultLauncher.launch(enableIntent);
} else {
getBonded();
}
btActivityResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
getBonded();
}
});
Тут вопрос от знатока. Можно же mBtAdapter.enable() использовать, почему так сложно ?
Вариант выше не требует дополнительных разрешений. Метод enabled() становиться депрекайтед в 13-м андроиде. Пример выше основан на рекомендованной альтернативе. Для предыдущих версий в манифесте должен быть еще пермишен BLUETOOTH_ADMIN. Но главное из-за выделенного жирным в документации
Bluetooth should never be enabled without direct user consent.
легко попасть под reject (отклонение обновления или нового приложения) или словить снятие с публикации.
Вернемся к выбору из списка сопряженных.
Мы получили список объектов типа BluetoothDevice, а нам нужно показать имя и узнать mac:
BluetoothDevice d = getItem(position);
String name = d.getName(); // требует BLUETOOH_CONNECT
String mac = d.getAddress(); // а это удивительно нет
Получается ли , что мы обошлись без необходимости в геолокации?
Так выглядит запрос BLUETOOH_CONNECT в Android 12.
Единственное чего мы достигли, пользователи более ранних версий останутся в неведении. Напомню, что пермишен нужен и для работы МНК ( .getRemoteDevice(), .connect() ).
Почему же так?
У вас сопряжен с телефоном телевизор, колонки . Программа это увидела и если у нее есть биг дата по пользователям, то как минимум она вычислит Ваш город. Если мало мобильное устройство в зоне досягаемости (удалось к нему подключиться), то можно местоположение сузить до 100 метров.
Именно о таком теоретическом возможном риске Вас предупреждают.
У меня Android 6-11. Почему же я вижу запрос к местоположению?
Мы рассмотрели вариант, когда Вы предварительно сделали сопряжение, не все пользователи могут сделать этот шаг самостоятельно. Часто для простоты даже не смотрят в список сопряженных, а начинают опрос эфира.
До 6-го андроида разрешения предоставлялись автоматически по факту упоминания в манифесте. Потом разрешения решили поделить, условно безопасные так и остались, а остальные стало требоваться запрашивать явно. В коде программ появился костыль вида:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission. …) != PackageManager.PERMISSION_GRANTED ){
…
}
}
Пользователи стали видеть запросы.
Процесс опроса эфира асинхронный.
1) Создаем слушателя
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// Найдено
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
…… делаем с ним что нужно
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
// процесс поиска завершен
}
}
};
2) Регистрируем слушателя сообщений.
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(mReceiver, filter);
3) Запускаем процесс
mBtAdapter.startDiscovery();
Прочитать документацию Вы можете самостоятельно.
Выскажу свое мнение почему там сбоку прикрутили геолокацию. Так у нас два пермишена BLUETOOTH слишком общий, BLUETOOTH_ADMIN нужен для изменения статуса и позволяет сканировать. Сделать его явно запрашиваемым, поломается много программ. У нас тут еще есть ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION, которые нужно явно запрашивать. Так может скрестим и по смыслу подходят.
И вот пошли шатания от версии к версии. Нужно ли именно FINE или хватит COARSE.
В 12м нужна связка BLUETOOTH_SCAN & ACCESS_FINE_LOCATION.
В андроиде есть неудобная для программистов практика без ошибки игнорировать действия, которые потом решили запретить и/или возвращать пустой/фиктивный результат. Делается это для того, чтобы устаревшие программы не завершились аварийно с ошибкой. Да еще вендоры могут внести свое видение того как правильно.
Кроме того в момент первоначальной настройки нового телефона/приставки можно запретить всем приложениям доступ к местоположению. После этого узнать, что всем или конкретно нам запрещено нет возможности. Все методы отрабатывают без ошибок, а бродкаст не приходит ;(
Вопрос от знатока. А почему не написали про Companion Device Manager (CDM)?
Когда я про неё прочитал, тоже подумал, что вот оно. Именно это решит проблему с пользователями. А вот реальность подкачала.
К сожалению документация, а конкретно примеры для java немного устарели (использованы депрекейтед StartIntentSenderForResult и onActivityResult), поэтому приведу уже поправленные коды.
Код поправлен на поиск всех устройств поблизости. Не важно есть у них имя или нет и какие типы интерфейсов поддерживают. В данном случае нас интересуют только само поведение "подружить" . Также для простоты minSDK поставлен от 8.0.
Если мы посмотрим в исходные коды операционной системы (Android SDK), то увидем:
public final class CompanionDeviceManager {
public void associate(
@NonNull AssociationRequest request,
@NonNull Callback callback,
@Nullable Handler handler) {
if (!checkFeaturePresent()) {
return;
}
так нелюбимое мною умирание молча . Что мешало сперва проверить callback, и если не работает вызвать failure ? Даже если это исправят, в предыдущих версиях андроида проблема останется :(
А причина ? В функции проверяется, что внутренняя переменная mService не null.
Конструктор принимает параметр службы как @Nullable. Получаем мы этот объект уже готовым:
CompanionDeviceManager deviceManager
= context.getSystemService(CompanionDeviceManager.class);
Наш объект существует, но на практике часто приходит не работоспособным. И получается нажали на кнопку "подружить с новым" и никакой реакции. Это первое мое разочарование.
Failure вообще оказался неинформативным. Вызывается только при отказе выбора. Текст ошибки всегда один и тот же.
Запустите предложенный демо пример.
Работает правильно, только если определение местоположения включено .
Если что-то из показанных стрелками выключено, диалог просто не показывается. Никаких ошибок в callback не передается.
И пришли мы к тому, что должны пользователю показать диалог
Включи! Определение местоположения.
Это меня окончательно разочаровало.
Выводы
Блютуз не может работать без геолокации. Ничего не поменялось с появлением альтернатив.
Внедрять их все же придется, чтобы приложение соответствовало правилам Google Play.
Как минимум учесть новые разрешения для работы с блуютуз.
Комментарии (15)
johnfound
08.07.2022 00:37А почему у меня bluetooth работает без местоположения. И вообще-то я именно так и использую его, а местоположение включаю очень редко. Телефон Redmi какой-то...
402d Автор
08.07.2022 08:35с 8го до 11го андроид сопряжение через компаин и общение с бле/классик не сопровождается внешними эфектами. Пермишены на локейшен не нужны. Но тут или пользователи будут ругать программу. Найти. Жму - оно ничего не делает.
поэтому перед вызовом associate(), проверять приходиться из кода самостоятельно. Статус блютуз и геолокацию. И тут уже самостоятельно писать пользователю. Откройте шторку . Включите геолокацию. Круг замкнулся. Параноик ее выключил - А приложение ее просить включить.
DaemonGloom
08.07.2022 10:12+1У вас работает bluetooth (т.е. наушники, передача файлов, мыши/клавиатуры/джойстики) или приложения, использующие bluetooth для своих целей (связь с конкретным устройством типа принтеров или иных вещей, функционал которых отсутствует в системе изначально)?
С первым в android проблем нет, только со вторым.johnfound
08.07.2022 16:03Ага, кажется что понял. Google гадит – ничего странного. Я куплю телефон с Линукс. Как только завезут...
Paulus
08.07.2022 04:12Спасибо за статью! Несколько лет назад после смены одного Андроида на другой тоже удивился, что программа управления отоплением через BT внезапно стала требовать координат. Думал, что баг в новой версии, а оказывается это фича
larasage
08.07.2022 07:40+1Выдается разрешение приложению на определение местоположения, спаривается с устройством, разрешение отбирается. У жены браслет так подключал.
Ну а про видимость MAC-адресов... Кто сказал, что они уникальные? :)
NickH12
08.07.2022 21:54android:usesPermissionFlags="neverForLocation"
Не работает?
402d Автор
09.07.2022 08:33BLUETOOTH_SCAN . Когда я стал свой код отлаживать под 12-й, то это разрешение у меня потребовалось только для одной функции
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { if (defaultAdapter.isDiscovering()) { defaultAdapter.cancelDiscovery(); } }
просто обернул. Пожертвовав надежностью работы. У меня служба печати. Небольшая теоретическая возможность была, что другая программа запустила сканирование.
У меня в манифесте 4 пермишена. bluetooth, bluetooth_admin ограничены 30, bluetooth_connect для новых. И пермишен просящий приблизительное положение в версиях ниже андроида 8.0. Выше работаю через компанион.
Lebets_VI
Позвольте мне:), я :),я :) ,я :),но сегодня без матов (и так замисули :)
ТС, я искренне понимаю Ваше негодование, но:
-- "Блютуз не может работать без геолокации " - может, но "гугл решил что нет" (С) (квест: почему?)
-- "Включи! Определение местоположения." - хочешь юзать - исполняй, иначе ну просто не будет работать, почему? - (С) - читай первый пункт ;).
Остальное - всего лишь следствие и разрыв шаблона, на самом деле всё гораздо... (каждый эксплуататор БТ вставит подходящий набор слов вместо троеточия).
Реально, искренне желаю принять приколы БТ и стать философом используя эту технологию.
Ztare
Объяснили же. Знание онлайна даже одного блютуз мак адреса уже раскрывает ваше местоположение. Т.к. остальные девайсы могут сопоставить местоположение этого же мак адреса с гео и отправить на тотже сервер
Lebets_VI
Во-первых, спасибо за минусы.
Во-вторых: Мне не нужно ничего объяснять, т.к. я наизусть знаю спецификацию.
Во-третьих: "Знание онлайна даже одного блютуз мак адреса уже раскрывает ваше местоположение" - откуда такие выводы? Почитайте спецификацию, а именно, что такое в блютусе мак-адрес. Он, если что, никакого отношения не имеет к общепринятому определению мак-адресов. А если это проблема из-за гугла, то scuzi, опять же, это проблема гугла, а не технологии вообще.
Еще раз повторяю, если гугл так решил, это не значит что это распространяется на сам блютус.
Кстати ТСу и за статью инкремент сделал и карму плюсанул.
Ztare
(Минусы не мои, прав нет) Очень просто - у вас дома условная кофеварка с блютусом. Ее адрес вы вбили в приложении (спарили, нашли etc). Этот же адрес видит условная машина яндекса сканирующая сети и картографирующая город. Вот теперь когда адрес виден в приложении как онлайн то сервер этого приложения может узнать у условного яндекса где вы с точностью до десятка метров. Понятно в целом нюансов много, но грубо так может работать.
Аналогично сбором сведений о положении адресов могут заниматься другие смартфоны с включенным гео и блютусом
Lebets_VI
Даже приняв невероятное, а именно то, что кофеварка будет видна в эфире будучи спаренной и в это время Яндекс-машина подъедет к кофеварке ближе чем на 10 метров, это не противоречит тому, что именно Гугл (ни яблоко, ни Микрософт ни кто-то другой) привязывает разрешение работы блютуса к местоположению. Наверное все таки у гугла какие-то хитрые планы:)
А я всего лишь сказал, что Гугл «редиска» :) и тут ничего не сделаешь. (Хотя этот прикол не на всех Андроидах соблюдается, но это уже другая история)
Ztare
Блютуз до 60 метров и в мобильниках умеет, думаю яндекс сможет и 100 через дома. Редмондовое блютус барахло именно так работает - всегда ищется. Гугл делает все возможное кроме реально важного )
402d Автор
Не туда смотрите. Есть такой и подобные им облачные сервисы https://coolkit-technologies.github.io/eWeLink-API/#/en/APIReferenceV2 . И есть куча оборудования для умного дома. https://aliexpress.ru/popular/ewelink-smart-switch.html В таких устройствах есть bluetooth, wifi и оно еще в облаке. Детские смарт часы , фитнес браслеты, электросамокаты. А где и главное кому сервера принадлежат ? Китай. А гугл чей ?