Хорошими делами прославиться нельзя

от автора

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

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<ScanFilter> 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 должен быть такой же, как при вещании, иначе вы не будете получать вообще никакой информации. Он уникален для вашего приложения. Сгенерировать можно вот тут https://www.uuidgenerator.net

В самой службе следует установить удобные интервалы опроса, у нас это раз в 30 секунд. Это позволяет нам беречь заряд батареи.
Приложение получилось не прожорливым. Даже с учетом работы локации, находится в конце списка по потреблению.

После нескольких месяцев разработки появились iOS и Android версии приложения. Мы отправили их на проверку в Google и Apple.
И вот тут началось самое интересное, оказывается разработать решение – это самое лёгкое. Сложности начались с размещением в сторах.
В Google у нас получилось пройти, предоставив бумагу официальных гос. органов, что они не против приложения. Получить её в России само по себе подвиг, но это уже другая история.
С Apple была тишина долгое время. Мы писали повторно, оправляли им разрешение гос. органов, но ответа не было.
И вот, в один прекрасный день, звонок. Мне позвонили из Apple! Я так переволновался, что забыл английский язык. В Apple были крайне вежливы и после пяти минут общения сказали, что найдут переводчика.
Через некоторое время, мне позвонили опять и уже на русском с акцентом стали спрашивать о приложении.
Сказали, что их медицинский департамент заинтересовался нашим приложением, что без их одобрения мы не пройдем проверку. Что если мы ответим на их вопросы, они готовы даже посотрудничать. После этого мы какое-то время покорно отвечали на все их вопросы и ждали решения и долгожданного сотрудничества.
Но чтобы не терять времени, мы решили рассказать о приложении нашим властям, чтобы показать альтернативу и возможность не принуждать людей как стадо, а дать им выбор и добровольный инструмент.
Отправляя письма во все инстанции, мы получали один ответ – полное отсутствие ответа.
Минздрав России мы решили атаковать активнее как Энди Дюфрейн. Но Минздрав был непробиваем, нам даже Госдепартамент США ответил на обращение, а вот Минздрав…
Нашему удивлению не было предела, когда Apple выпустила своё API ExposureNotification с формулировками точь-в-точь, повторяющими наши! Может быть это совпадение, но это настораживает и заставляет задуматься.

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

https://mysafe.ai

Google Play

ссылка на оригинал статьи https://habr.com/ru/post/504606/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *