· 3 dk okuma

İnternet olmadan da denemeye devam eden bir acil durum butonu

yazan: · Proje: The Finger of Hope

#android #java #flutter #typescript

The Finger of Hope bir acil durum uyarı uygulaması. En fazla üç güvendiğiniz kişiyle eşleşiyorsunuz. Bir şeyler ters gittiğinde tek bir basış onlara konumunuzla birlikte bir uyarı gönderiyor, sonrasında da "güvendeyim" güncellemesi gönderebiliyorsunuz. Kesin gereksinim şu: uyarı, internet olmadığı durumlar da dahil olmak üzere mevcut her yolu denemek zorunda.

Üç ayrı codebase var: Huawei Mobile Services üzerine kurulu native bir Java uygulaması (Google servisleri olmayan Huawei telefonlar için), Google cihazları için bir Flutter uygulaması ve bir TypeScript sunucusu.

Mesajı ulaştırmak için bir durum makinesi

Gönderim, her transport için bir deneme durumu ve bir bekleme durumu olan bir durum makinesi olarak modellendi:

  1. Kişilere push bildirimleri ileten HTTPS üzerinden sunucu.
  2. Tek bir 160 karakterlik mesaja sığan kompakt bir metin formatıyla SMS.
  3. Telefondan telefona Wi-Fi Direct.
  4. Uygulamayı çalıştıran diğer telefonlar üzerinden aktarma da dahil olmak üzere Bluetooth Low Energy.
  5. Huawei cihazlarda Huawei Nearby.

Her beklemenin kendi timeout'u var: sunucu için bir dakika, yerel transport'lar için 10 ila 30 saniye. Timeout, makineyi bir sonraki yola geçiriyor. Cihazın bağlantısı olmadığını bildiği durumlar için sadece çevrimdışı çalışan bir mod da var. Bu mod doğrudan Wi-Fi Direct ve Bluetooth'a geçiyor ve toplam iki dakikalık bir sınır içinde, giderek artan gecikmelerle (yakındaki telefonlar çakışmasın diye biraz da rastgelelik ekleyerek) tekrar deniyor.

Bluetooth ya da Wi-Fi üzerinden yapılan bir gönderim, ancak alıcı 20 saniye içinde bir teslim raporu geri gönderdiğinde teslim edilmiş sayılıyor. Aktarma yapan telefonlar bu raporu göndericiye doğru geri iletiyor.

Mesajları dar kanallara sığdırmak

Transport'ların limitleri birbirinden çok farklı; bu yüzden mesajlar parçalara bölünüyor: Bluetooth için 512 byte, Wi-Fi Direct için 64 KB, header için de yer ayrılarak. Alıcı parçaları bir zaman sınırı ve gönderici başına bir bellek limitiyle yeniden birleştiriyor; böylece hatalı davranan bir cihaz, başka bir telefonun belleğini yarım kalmış mesajlarla dolduramıyor.

Aktarmanın kuralları olmalı, yoksa mesh bir yankı odasına dönüşür. Mesajlar beş dakika ve on beş hop sonra geçersiz oluyor, tekrarlanan mesajlar belirli bir zaman penceresi içinde atılıyor ve sürekli hatalı trafik gönderen cihazlar geçici olarak karantinaya alınıyor.

Gerçek Android cihazlarda anahtarlar

Şifreleme katmanı, anahtar anlaşması için X25519, imzalar için Ed25519 ve payload için AES-256-GCM kullanıyor; anahtarlar Android Keystore'da tutuluyor. Bu plan, eski Android sürümlerinde gerçeklerle karşılaşıyor: Keystore bu anahtar türlerini yalnızca Android 13'ten (API 33) itibaren destekliyor ve Android 10'da anahtar anlaşması istemek doğrudan başarısız oluyor. Uygulama bunu tespit edip P-256 anahtarlarına geçiyor; Keystore'un bu işi yapamadığı cihazlarda bu anahtarlar şifreli depolamada tutuluyor.

Android'in bana öğrettiği diğer şeyler

  • Timer'lar kendi thread'lerinde. Bazı üreticilerin güç yönetimi, main thread'deki timer'ları durduruyor; bu da timeout'lar üzerine kurulu bir durum makinesi için ölümcül.
  • Foreground service zamanlaması. Yedek akış, foreground service önce kayıt olabilsin diye kısa bir gecikmeyle başlıyor. Aksi hâlde Android, uyarı daha başlamadan exception fırlatıyor.
  • Bluetooth tuhaflıkları. Uygulama, genel GATT hatası 133'te yeniden bağlanıyor, parçalamadan önce bağlantının daha büyük bir paket boyutu üzerinde anlaşmasını bekliyor ve 20 byte'lık parçaları uzunluk önekli frame'ler hâlinde yeniden birleştiriyor.
  • "Bağlı" olmak çevrimiçi olmak demek değil. Bağlantı kontrolleri, Android'in ağı doğrulayıp doğrulamadığına ve bir captive portal olup olmadığına bakıyor, ardından sunucu yoluna güvenmeden önce gerçek bir bağlantı deniyor.

Sunucu

Sunucu, PostgreSQL ile Express ve TypeScript. Hesapları, cihazları, eşleşme isteklerini ve kabul/red işlemlerini, WebSocket üzerinden konum paylaşımını ve bildirim teslimini kapsıyor. Push bildirimleri, retry ve exponential backoff içeren Redis tabanlı bir job kuyruğundan geçiyor ve alıcının telefonuna göre Firebase Cloud Messaging ya da Huawei Push Kit üzerinden gönderiliyor. Docker Compose; API'yi, bir worker'ı, PostgreSQL'i, Redis'i, Prometheus'u ve Grafana'yı ayağa kaldırıyor.

Son durum

Uygulamayı Huawei'nin İnovasyon Kategorisi yarışması için geliştirdim. 2025'te birinci, 2026'da ikinci oldu ve 2026 yarışması bittiğinde geliştirmeyi durdurdum. O noktada temel özellikler tamamlanmış ve çalışır durumdaydı: uyarılar, transport zinciri, parçalama, aktarma ve sunucu. Şifreleme katmanı yazıldı ama gönderim yoluna hiç bağlanmadı, yakındaki hastaneleri ve karakolları göstermek plan olarak kaldı; Flutter uygulaması ise çevrimdışı altyapının sadeleştirilmiş bir versiyonunu içeriyor. Kaynak kod: Huawei uygulaması, Android uygulaması, sunucu.