Alvaros
.
- Регистрация
- 14.05.16
- Сообщения
- 21.452
- Реакции
- 101
- Репутация
- 204
Хочу вам рассказать нашу историю из серии «ожидание и реальность» или о том, как слова товарища М.Ф. Квинтилиана: «Вредить легко, помогать трудно» приобрели для нас новый смысл.
Covid-19. Наверное, каждый слышал об этом вирусе. Я сейчас не берусь описывать свое личное отношение или обсуждать теории заговоров вокруг этого. Лично для меня это реальный кейс с близкими, которые заболели.
Столкнувшись с ковид лицом к лицу, наша команда решила внести свой вклад в борьбу с этим злом.
Мы разработали мобильное приложение, которое позволяет отслеживать местоположение опасных районов с ковид, с использованием данных о локации и по Bluetooth.
В отличие от аналогов, мы хотели сделать всё анонимно и добровольно. Без использования регистрации, привязки номера телефона и email.
Без отправки данных во все возможные инстанции.
Идея была проста – чтобы каждый мог отправить свой статус здоровья и поделиться им с другими участниками добровольно. Чтобы пользователи могли видеть на карте зараженные маршруты и получать уведомления после пересечения с заболевшими. Интегрировали туда индекс заражения района и множество других, на наш взгляд, полезных идей.
Технических сложностей было много, начиная от работы локации в фоновом режиме и заканчивая обменом данных по Bluetooth без подключения.
Так как Bluetooth может вызвать много вопросов и, на наш взгляд, наиболее интересен, поделюсь хитрым решением с вами. Может кому-то пригодится в проектах.
Мы использовали Bluetooth одновременно как Beacon-маячок и как сканер маячков.
Наша задача находить контакты с заболевшими людьми рядом. Так как у нас анонимное приложение и у каждого пользователя есть только обезличенный ID, его мы и решили вещать по Bluetooth.
В Android для этого мы создали foreground службу BluetoothMonitorService. Эта служба должна вещать наш ID по Bluetooth и сканировать местность рядом на наличие других ID. В обычной службе операционная система не даст работать вашему приложению и, к сожалению, должно быть постоянное уведомление в трее.
Вторым важным моментом на этом этапе требуется наличие разрешения:
Manifest.permission.ACCESS_COARSE_LOCATION.
Так что локацию запрашивать придется. Без этого разрешения вам будет приходить пустой список.
Ну и конечно:
android.permission.BLUETOOTH
android.permission.BLUETOOTH_ADMIN
И так, начнем.
Для начала запустим вещание:
private void StartAdvertisingLegacy(long timeoutInMillis, int id) {
byte[] bytesPID = ByteBuffer.allocate(charLength).putInt(id).array();
data = new AdvertiseData.Builder()
.setIncludeDeviceName(false)
.setIncludeTxPowerLevel(false)
.addServiceUuid(pUuid)
.addManufacturerData(9, bytesPID)
.build();
try {
Log.d(TAG, "Start advertising");
advertiser = (advertiser != null) ? advertiser : BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
advertiser.startAdvertising(settings, data, advertisingCallback);
} catch (Exception e) {
Log.d(TAG, "Failed to start advertising legacy: ${e.message}");
}
}
Как можно заметить ID пользователя мы разместили в addManufacturerData.
Будьте внимательны, размер пакета ограничен 31 байтом. Отключите отображение имени и информацию об уровень мощности, чтобы хватило на ваш Payload
addServiceUuid – это 128-битный уникальный идентификатор атрибута. Уникальный UUID для нашего приложения, по которому можно будет отфильтровать девайс при сканировании.
После этого слушаем ответ:
AdvertiseCallback advertisingCallback = new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
super.onStartSuccess(settingsInEffect);
Log.i(TAG, "Advertising onStartSuccess");
Log.i(TAG, settingsInEffect.toString());
isAdvertising = true;
}
@Override
public void onStartFailure(int errorCode) {
super.onStartFailure(errorCode);
String reason;
switch (errorCode) {
case ADVERTISE_FAILED_ALREADY_STARTED:
reason = "ADVERTISE_FAILED_ALREADY_STARTED";
isAdvertising = true;
break;
case ADVERTISE_FAILED_FEATURE_UNSUPPORTED:
reason = "ADVERTISE_FAILED_FEATURE_UNSUPPORTED";
isAdvertising = false;
break;
case ADVERTISE_FAILED_INTERNAL_ERROR:
reason = "ADVERTISE_FAILED_INTERNAL_ERROR";
isAdvertising = false;
break;
case ADVERTISE_FAILED_TOO_MANY_ADVERTISERS:
reason = "ADVERTISE_FAILED_TOO_MANY_ADVERTISERS";
isAdvertising = false;
break;
case ADVERTISE_FAILED_DATA_TOO_LARGE:
reason = "ADVERTISE_FAILED_DATA_TOO_LARGE";
isAdvertising = false;
charLength--;
break;
default:
reason = "UNDOCUMENTED";
}
Log.e(TAG, "Advertising onStartFailure: " + errorCode + " " + reason);
}
};
Параллельно запускаем наш сканер:
public void StartScan(ScanCallback scanCallback) {
ScanFilter filter = new ScanFilter.Builder()
.setServiceUuid(new ParcelUuid(UUID.fromString(serviceUUID)))
.build();
List filters = new ArrayList<>();
filters.add(filter);
ScanSettings settings = new ScanSettings.Builder()
.setReportDelay(reportDelay)
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.build();
this.scanCallback = scanCallback;
//try to get a scanner if there isn't anything
scanner = (scanner != null) ? scanner : BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();
scanner.startScan(filters, settings, scanCallback);
Log.d(TAG, "scanning started");
}
ServiceUUID должен быть такой же, как при вещании, иначе вы не будете получать вообще никакой информации. Он уникален для вашего приложения. Сгенерировать можно вот тут
В самой службе следует установить удобные интервалы опроса, у нас это раз в 30 секунд. Это позволяет нам беречь заряд батареи.
Приложение получилось не прожорливым. Даже с учетом работы локации, находится в конце списка по потреблению.
После нескольких месяцев разработки появились iOS и Android версии приложения. Мы отправили их на проверку в Google и Apple.
И вот тут началось самое интересное, оказывается разработать решение – это самое лёгкое. Сложности начались с размещением в сторах.
В Google у нас получилось пройти, предоставив бумагу официальных гос. органов, что они не против приложения. Получить её в России само по себе подвиг, но это уже другая история.
С Apple была тишина долгое время. Мы писали повторно, оправляли им разрешение гос. органов, но ответа не было.
И вот, в один прекрасный день, звонок. Мне позвонили из Apple! Я так переволновался, что забыл английский язык. В Apple были крайне вежливы и после пяти минут общения сказали, что найдут переводчика.
Через некоторое время, мне позвонили опять и уже на русском с акцентом стали спрашивать о приложении.
Сказали, что их медицинский департамент заинтересовался нашим приложением, что без их одобрения мы не пройдем проверку. Что если мы ответим на их вопросы, они готовы даже посотрудничать. После этого мы какое-то время покорно отвечали на все их вопросы и ждали решения и долгожданного сотрудничества.
Но чтобы не терять времени, мы решили рассказать о приложении нашим властям, чтобы показать альтернативу и возможность не принуждать людей как стадо, а дать им выбор и добровольный инструмент.
Отправляя письма во все инстанции, мы получали один ответ – полное отсутствие ответа.
Минздрав России мы решили атаковать активнее как Энди Дюфрейн. Но Минздрав был непробиваем, нам даже Госдепартамент США ответил на обращение, а вот Минздрав…
Нашему удивлению не было предела, когда Apple выпустила своё API ExposureNotification с формулировками точь-в-точь, повторяющими наши! Может быть это совпадение, но это настораживает и заставляет задуматься.
Хочу обратиться к вам за помощью в оценке приложения, в его распространении и донесении до наших властей, что можно и по-другому. Давайте попробуем вместе что-то поменять!
Covid-19. Наверное, каждый слышал об этом вирусе. Я сейчас не берусь описывать свое личное отношение или обсуждать теории заговоров вокруг этого. Лично для меня это реальный кейс с близкими, которые заболели.
Столкнувшись с ковид лицом к лицу, наша команда решила внести свой вклад в борьбу с этим злом.
Мы разработали мобильное приложение, которое позволяет отслеживать местоположение опасных районов с ковид, с использованием данных о локации и по Bluetooth.
В отличие от аналогов, мы хотели сделать всё анонимно и добровольно. Без использования регистрации, привязки номера телефона и email.
Без отправки данных во все возможные инстанции.
Идея была проста – чтобы каждый мог отправить свой статус здоровья и поделиться им с другими участниками добровольно. Чтобы пользователи могли видеть на карте зараженные маршруты и получать уведомления после пересечения с заболевшими. Интегрировали туда индекс заражения района и множество других, на наш взгляд, полезных идей.
Технических сложностей было много, начиная от работы локации в фоновом режиме и заканчивая обменом данных по Bluetooth без подключения.
Так как Bluetooth может вызвать много вопросов и, на наш взгляд, наиболее интересен, поделюсь хитрым решением с вами. Может кому-то пригодится в проектах.
Мы использовали Bluetooth одновременно как Beacon-маячок и как сканер маячков.
Наша задача находить контакты с заболевшими людьми рядом. Так как у нас анонимное приложение и у каждого пользователя есть только обезличенный ID, его мы и решили вещать по Bluetooth.
В Android для этого мы создали foreground службу BluetoothMonitorService. Эта служба должна вещать наш ID по Bluetooth и сканировать местность рядом на наличие других ID. В обычной службе операционная система не даст работать вашему приложению и, к сожалению, должно быть постоянное уведомление в трее.
Вторым важным моментом на этом этапе требуется наличие разрешения:
Manifest.permission.ACCESS_COARSE_LOCATION.
Так что локацию запрашивать придется. Без этого разрешения вам будет приходить пустой список.
Ну и конечно:
android.permission.BLUETOOTH
android.permission.BLUETOOTH_ADMIN
И так, начнем.
Для начала запустим вещание:
private void StartAdvertisingLegacy(long timeoutInMillis, int id) {
byte[] bytesPID = ByteBuffer.allocate(charLength).putInt(id).array();
data = new AdvertiseData.Builder()
.setIncludeDeviceName(false)
.setIncludeTxPowerLevel(false)
.addServiceUuid(pUuid)
.addManufacturerData(9, bytesPID)
.build();
try {
Log.d(TAG, "Start advertising");
advertiser = (advertiser != null) ? advertiser : BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
advertiser.startAdvertising(settings, data, advertisingCallback);
} catch (Exception e) {
Log.d(TAG, "Failed to start advertising legacy: ${e.message}");
}
}
Как можно заметить ID пользователя мы разместили в addManufacturerData.
Будьте внимательны, размер пакета ограничен 31 байтом. Отключите отображение имени и информацию об уровень мощности, чтобы хватило на ваш Payload
addServiceUuid – это 128-битный уникальный идентификатор атрибута. Уникальный UUID для нашего приложения, по которому можно будет отфильтровать девайс при сканировании.
После этого слушаем ответ:
AdvertiseCallback advertisingCallback = new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
super.onStartSuccess(settingsInEffect);
Log.i(TAG, "Advertising onStartSuccess");
Log.i(TAG, settingsInEffect.toString());
isAdvertising = true;
}
@Override
public void onStartFailure(int errorCode) {
super.onStartFailure(errorCode);
String reason;
switch (errorCode) {
case ADVERTISE_FAILED_ALREADY_STARTED:
reason = "ADVERTISE_FAILED_ALREADY_STARTED";
isAdvertising = true;
break;
case ADVERTISE_FAILED_FEATURE_UNSUPPORTED:
reason = "ADVERTISE_FAILED_FEATURE_UNSUPPORTED";
isAdvertising = false;
break;
case ADVERTISE_FAILED_INTERNAL_ERROR:
reason = "ADVERTISE_FAILED_INTERNAL_ERROR";
isAdvertising = false;
break;
case ADVERTISE_FAILED_TOO_MANY_ADVERTISERS:
reason = "ADVERTISE_FAILED_TOO_MANY_ADVERTISERS";
isAdvertising = false;
break;
case ADVERTISE_FAILED_DATA_TOO_LARGE:
reason = "ADVERTISE_FAILED_DATA_TOO_LARGE";
isAdvertising = false;
charLength--;
break;
default:
reason = "UNDOCUMENTED";
}
Log.e(TAG, "Advertising onStartFailure: " + errorCode + " " + reason);
}
};
Параллельно запускаем наш сканер:
public void StartScan(ScanCallback scanCallback) {
ScanFilter filter = new ScanFilter.Builder()
.setServiceUuid(new ParcelUuid(UUID.fromString(serviceUUID)))
.build();
List filters = new ArrayList<>();
filters.add(filter);
ScanSettings settings = new ScanSettings.Builder()
.setReportDelay(reportDelay)
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.build();
this.scanCallback = scanCallback;
//try to get a scanner if there isn't anything
scanner = (scanner != null) ? scanner : BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();
scanner.startScan(filters, settings, scanCallback);
Log.d(TAG, "scanning started");
}
ServiceUUID должен быть такой же, как при вещании, иначе вы не будете получать вообще никакой информации. Он уникален для вашего приложения. Сгенерировать можно вот тут
You must be registered for see links
В самой службе следует установить удобные интервалы опроса, у нас это раз в 30 секунд. Это позволяет нам беречь заряд батареи.
Приложение получилось не прожорливым. Даже с учетом работы локации, находится в конце списка по потреблению.
После нескольких месяцев разработки появились iOS и Android версии приложения. Мы отправили их на проверку в Google и Apple.
И вот тут началось самое интересное, оказывается разработать решение – это самое лёгкое. Сложности начались с размещением в сторах.
В Google у нас получилось пройти, предоставив бумагу официальных гос. органов, что они не против приложения. Получить её в России само по себе подвиг, но это уже другая история.
С Apple была тишина долгое время. Мы писали повторно, оправляли им разрешение гос. органов, но ответа не было.
И вот, в один прекрасный день, звонок. Мне позвонили из Apple! Я так переволновался, что забыл английский язык. В Apple были крайне вежливы и после пяти минут общения сказали, что найдут переводчика.
Через некоторое время, мне позвонили опять и уже на русском с акцентом стали спрашивать о приложении.
Сказали, что их медицинский департамент заинтересовался нашим приложением, что без их одобрения мы не пройдем проверку. Что если мы ответим на их вопросы, они готовы даже посотрудничать. После этого мы какое-то время покорно отвечали на все их вопросы и ждали решения и долгожданного сотрудничества.
Но чтобы не терять времени, мы решили рассказать о приложении нашим властям, чтобы показать альтернативу и возможность не принуждать людей как стадо, а дать им выбор и добровольный инструмент.
Отправляя письма во все инстанции, мы получали один ответ – полное отсутствие ответа.
Минздрав России мы решили атаковать активнее как Энди Дюфрейн. Но Минздрав был непробиваем, нам даже Госдепартамент США ответил на обращение, а вот Минздрав…
Нашему удивлению не было предела, когда Apple выпустила своё API ExposureNotification с формулировками точь-в-точь, повторяющими наши! Может быть это совпадение, но это настораживает и заставляет задуматься.
Хочу обратиться к вам за помощью в оценке приложения, в его распространении и донесении до наших властей, что можно и по-другому. Давайте попробуем вместе что-то поменять!
You must be registered for see links
You must be registered for see links



