НОВОСТИ Хорошими делами прославиться нельзя

Alvaros
Онлайн
Регистрация
14.05.16
Сообщения
21.452
Реакции
101
Репутация
204
Хочу вам рассказать нашу историю из серии «ожидание и реальность» или о том, как слова товарища М.Ф. Квинтилиана: «Вредить легко, помогать трудно» приобрели для нас новый смысл.

Covid-19. Наверное, каждый слышал об этом вирусе. Я сейчас не берусь описывать свое личное отношение или обсуждать теории заговоров вокруг этого. Лично для меня это реальный кейс с близкими, которые заболели.
Столкнувшись с ковид лицом к лицу, наша команда решила внести свой вклад в борьбу с этим злом.

-o-5zwbdd0h9smnczggecete2uq.png




Мы разработали мобильное приложение, которое позволяет отслеживать местоположение опасных районов с ковид, с использованием данных о локации и по 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 с формулировками точь-в-точь, повторяющими наши! Может быть это совпадение, но это настораживает и заставляет задуматься.

Хочу обратиться к вам за помощью в оценке приложения, в его распространении и донесении до наших властей, что можно и по-другому. Давайте попробуем вместе что-то поменять!



 
Сверху Снизу