Skip to content

Danger Mode Architecture

NailLaraqui edited this page Dec 16, 2025 · 13 revisions

This page describes the complete architecture of Warnastrophy's Danger Mode, a core feature that automatically detects emergency situations and triggers appropriate safety actions.

Overview

Danger Mode is an automated safety system that:

  • Detects hazardous areas (hazards)
  • Monitors the user's physical activity via sensors
  • Automatically alerts emergency contacts
  • Triggers emergency calls and text messages
  • Allows for optional voice confirmation

Global architecture

┌─────────────────────────────────────────────────────────────────┐
│                    DangerModeOrchestrator                       │
│              (Central coordination)                             │
└──────┬────────────┬────────────┬────────────┬────────────┬──────┘
       │            │            │            │            │
       ▼            ▼            ▼            ▼            ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Danger   │ │Movement  │ │  User    │ │   GPS    │ │ Contacts │
│  Mode    │ │ Service  │ │  Prefs   │ │ Service  │ │   Repo   │
│ Service  │ │          │ │          │ │          │ │          │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
       │            │            │            │            │
       ▼            ▼            ▼            ▼            ▼
┌──────────────────────────────────────────────────────────────────┐
│                     Emergency Actions                            │
│                   (SMS + Calls)                                  │
└──────────────────────────────────────────────────────────────────┘

Main components

1. DangerModeService

Responsibility: Managing the Danger Mode status

Danger Mode State

data class DangerModeState(
    val isActive: Boolean = false,
    val activationTime: TimeSource.Monotonic.ValueTimeMark? = null,
    val activatingHazard: Hazard? = null,  // null if manual activation
    val activity: Activity? = null,         // Configuration preset
    val capabilities: Set<DangerModeCapability> = emptySet(),
    val dangerLevel: DangerLevel = DangerLevel.LOW
)

Activation modes

Automatic activation :

Detected hazard → DangerModeService observes activeHazardFlow
                ↓
        activatingHazard != null
                ↓
        isActive = true

Manual activation :

User manually enables
                ↓
        activatingHazard = null
                ↓
        isActive = true

Deactivation :

Automatic : Hazard disappears + activation was auto
Manual : User explicitly disables

Capabilities and permissions

The capabilities define what the Danger Mode can do :

enum class DangerModeCapability {
    SMS,   // Can send emergency text messages
    CALL   // Can make emergency calls
}

Permission validation :

fun setCapabilities(capabilities: Set<DangerModeCapability>): Result<Unit> {
    for (cap in capabilities) {
        val requiredPermission = when (cap) {
            DangerModeCapability.SMS -> AppPermissions.SendEmergencySms
            DangerModeCapability.CALL -> null
        }
        
        // Check that the permission is granted
        if (permissionManager.getPermissionResult(perm) != PermissionResult.Granted) {
            return Result.failure(IllegalStateException("Missing permission"))
        }
    }
    
    _state.value = _state.value.copy(capabilities = validated)
    return Result.success(Unit)
}

Danger levels

enum class DangerLevel {
    LOW,      // Low risk
    MEDIUM,   // Medium risk
    HIGH,     // High risk
    CRITICAL  // Critical risk
}

The danger level can be used to:

  • Adjust the monitoring frequency
  • Modify detection thresholds
  • Prioritize notifications

2. MovementService

Responsibility: Detection of suspicious inactivity via motion sensors

State machine

        ┌──────────┐
        │   SAFE   │
        └────┬─────┘
             │
             │ Acceleration > preDangerThreshold
             ▼
      ┌─────────────┐
      │  PRE_DANGER │ ← Acceleration spike detected
      └──────┬──────┘
             │
             │ Acceleration ≤ dangerAverageThreshold
             ▼
   ┌──────────────────┐
   │ PRE_DANGER_ACC   │ ← Samples accumulation
   └────────┬─────────┘
            │
            │ Timeout reached
            ▼
      ┌─────────┐
      │ Evaluate│
      │ average │
      └────┬────┘
           │
           ├─── < dangerAverageThreshold ──→  ┌────────┐
           │                                  │ DANGER │
           │                                  └────────┘
           │
           └─── ≥ dangerAverageThreshold ──→  ┌──────┐
                                              │ SAFE │
                                              └──────┘

Movement states

sealed class MovementState(val timestamp: ComparableTimeMark) {
    class Safe(timestamp)          // Normal
    class PreDanger(timestamp)     // Spike detected, waiting for stabilization
    class PreDangerAcc(timestamp, val accumulatedSamples: MutableList<Double>)
    class Danger(timestamp)        // Confirmed suspicious inactivity
}

Detection configuration

data class MovementConfig(
    val preDangerThreshold: Double = 50.0,      // Spike treshold (m/s²)
    val dangerAverageThreshold: Double = 1.0,    // Average threshold (m/s²)
    val preDangerTimeout: Duration = 10.seconds // Observation duration
)

Example scenario - Fall:

Time    Acceleration    State
0s       1.2 m/s²       SAFE
1s       65.0 m/s²      PRE_DANGER (Fall spike)
2s       0.8 m/s²       PRE_DANGER_ACC (accumulation)
3s       0.7 m/s²       PRE_DANGER_ACC
...
10s      0.9 m/s²       PRE_DANGER_ACC
11s      -              DANGER (average 0.85 < 1.0)

Integration with DangerModeService

class MovementService(
    private val dangerModeStateFlow: StateFlow<DangerModeService.DangerModeState>?
) {
    init {
        dangerModeStateFlow?.let { stateFlow ->
            launch {
                stateFlow.collect { dangerModeState ->
                    // Automatic configuration update based on activity
                    dangerModeState.activity?.movementConfig?.let { newConfig ->
                        config = newConfig
                    }
                }
            }
        }
    }
}

Example of activities with different configurations :

object Activities {
    val WALKING = Activity(
        name = "Walking",
        movementConfig = MovementConfig(
            preDangerThreshold = 30.0,
            dangerAverageThreshold = 2.0,
            preDangerTimeout = 15.seconds
        )
    )
    
    val CYCLING = Activity(
        name = "Cycling",
        movementConfig = MovementConfig(
            preDangerThreshold = 60.0,
            dangerAverageThreshold = 1.5,
            preDangerTimeout = 8.seconds
        )
    )
    
    val DRIVING = Activity(
        name = "Driving",
        movementConfig = MovementConfig(
            preDangerThreshold = 80.0,
            dangerAverageThreshold = 1.0,
            preDangerTimeout = 5.seconds
        )
    )
}

3. DangerModeOrchestrator

Responsibility: Central coordination and initiation of emergency actions

Decision flow

┌─────────────────────────────────────────────────────────┐
│ Continuous assessment of conditions                     │
└───────────────────────┬─────────────────────────────────┘
                        │
                        ▼
        ┌───────────────────────────────┐
        │ Are all the conditions OK?    │
        │                               │
        │ ✓ DangerMode.isActive         │
        │ ✓ activatingHazard != null    │
        │ ✓ preferences.alertMode       │
        │ ✓ preferences.inactivityDet.  │
        │ ✓ MovementState = Danger      │
        └───────┬──────────────┬────────┘
                │              │
            Yes │              │ No
                │              └──→ [Do Nothing]
                ▼
    ┌───────────────────────┐
    │ Retrieve emergency    │
    │ number + position     │
    └───────┬───────────────┘
            │
            ▼
    ┌──────────────────────────┐
    │ Determine action based on│
    │ user preferences         │
    └───────┬──────────────────┘
            │
            ├─── SMS only ──────────┐
            │                       │
            ├─── Call only ─────────┤
            │                       │
            └─── SMS + Call ────────┤
                                    │
                                    ▼
                        ┌───────────────────┐
                        │ Voice confirmation│
                        │    enabled ?      │
                        └────┬──────────┬───┘
                             │          │
                         Yes │          │ No
                             │          │
                             ▼          ▼
                    ┌────────────┐  ┌────────────┐
                    │   Display  │  │  Execute   │
                    │   screen   │  │   action   │
                    │confirmation│  │directly    │
                    └──────┬─────┘  └────────────┘
                           │
                           ├─── "Yes" ──→ [Execute action]
                           │
                           └─── "No" ───→ [Cancel]

Orchestrator state

data class OrchestratorState(
    val isWaitingForConfirmation: Boolean = false,
    val pendingAction: PendingEmergencyAction? = null,
    val confirmationTimeoutSeconds: Int = 0,
    val lastActionTaken: EmergencyActionResult? = null
)

Emergency actions

sealed class PendingEmergencyAction {
    data class SendSms(
        val phoneNumber: String,
        val message: EmergencyMessage
    )
    
    data class MakeCall(
        val phoneNumber: String
    )
    
    data class SendSmsAndCall(
        val phoneNumber: String,
        val message: EmergencyMessage
    )
}

Continuous monitoring

fun startMonitoring() {
    monitoringJob = scope.launch {
        combine(
            dangerModeService.state,
            movementService.movementState,
            userPreferencesRepository.getUserPreferences
        ) { dangerState, movementState, preferences ->
            Triple(dangerState, movementState, preferences.dangerModePreferences)
        }
        .collectLatest { (dangerState, movementState, preferences) ->
            evaluateDangerConditions(dangerState, movementState, preferences)
        }
    }
}

Triggering conditions :

private suspend fun evaluateDangerConditions(...) {
    val shouldTrigger =
        dangerState.isActive &&
        dangerState.activatingHazard != null &&
        preferences.alertMode &&
        preferences.inactivityDetection &&
        movementState is MovementState.Danger
    
    if (!shouldTrigger) return
    
    // Avoid multiple triggerings
    if (_state.value.isWaitingForConfirmation || 
        _state.value.lastActionTaken != null) {
        return
    }
    
    triggerEmergencyProtocol(preferences)
}

4. Emergency services

SmsSender

Interface :

fun interface SmsSender {
    fun sendSms(phoneNumber: String, message: EmergencyMessage)
}

Implementation :

class SmsManagerSender(context: Context) : SmsSender {
    private val smsManager = 
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            context.getSystemService(SmsManager::class.java)
        } else {
            @Suppress("DEPRECATION") SmsManager.getDefault()
        }
    
    override fun sendSms(phoneNumber: String, message: EmergencyMessage) {
        require(phoneNumber.isNotBlank()) {
            "Phone number cannot be empty. Please add an emergency contact."
        }
        require(phoneNumber.any { it.isDigit() }) {
            "Invalid phone number format: $phoneNumber"
        }
        
        smsManager.sendTextMessage(
            phoneNumber,
            null,
            message.toStringMessage(),
            null,
            null
        )
    }
}

Message format :

data class EmergencyMessage(
    val location: Location,
    val timestamp: Long = System.currentTimeMillis()
) {
    fun toStringMessage(): String {
        return """
            🚨 EMERGENCY ALERT
            I need help!
            Location: https://maps.google.com/?q=${location.latitude},${location.longitude}
            Time: ${formatTimestamp(timestamp)}
        """.trimIndent()
    }
}

CallSender

Interface :

fun interface CallSender {
    fun placeCall(phoneNumber: String)
}

Implementation :

class CallIntentCaller(
    private val appContext: Context,
    private val defaultNumber: String
) : CallSender {
    override fun placeCall(phoneNumber: String) {
        val num = phoneNumber.ifBlank { defaultNumber }
        
        // Check CALL_PHONE permission
        val granted = ContextCompat.checkSelfPermission(
            appContext,
            android.Manifest.permission.CALL_PHONE
        ) == PackageManager.PERMISSION_GRANTED
        
        if (!granted) return
        
        val intent = Intent(Intent.ACTION_CALL).apply {
            data = "tel:$num".toUri()
            flags = Intent.FLAG_ACTIVITY_NEW_TASK
        }
        appContext.startActivity(intent)
    }
}

5. GpsService

Responsibility: Provide GPS location for emergency messages

GPS State

data class GpsPositionState(
    val position: LatLng = AppConfig.defaultPosition,
    val isLoading: Boolean = false,
    val result: GpsResult = GpsResult.Failed
)

sealed class GpsResult {
    object Failed
    data class Success(val message: String = "Success")
}

Location modes

Occasional mode :

fun requestCurrentLocation() {
    serviceScope.launch {
        try {
            setLoading(true)
            val request = CurrentLocationRequest.Builder()
                .setPriority(Priority.PRIORITY_HIGH_ACCURACY)
                .setMaxUpdateAgeMillis(0)
                .build()
            
            val location = locationClient.getCurrentLocation(request, null).await()
            if (location != null) {
                updatePosition(LatLng(location.latitude, location.longitude))
                setSuccess("Current position obtained")
            }
        } catch (e: SecurityException) {
            setError(ErrorType.LOCATION_NOT_GRANTED_ERROR)
        }
    }
}

Continuous mode :

fun startLocationUpdates() {
    val request = LocationRequest.Builder(
        Priority.PRIORITY_HIGH_ACCURACY,
        AppConfig.positionUpdateDelayMs
    ).build()
    
    locationClient.requestLocationUpdates(
        request,
        locationCallback,
        Looper.getMainLooper()
    )
}

Foreground Service Mode :

fun startForegroundLocationUpdates(
    service: Service,
    channelId: String,
    channelName: String,
    notificationId: Int
) {
    // Creating the notification channel
    val channel = NotificationChannel(
        channelId,
        channelName,
        NotificationManager.IMPORTANCE_LOW
    )
    
    // Persistent notification
    val notification = NotificationCompat.Builder(service, channelId)
        .setContentTitle("Location tracking active")
        .setContentText("Your location is being monitored")
        .setSmallIcon(android.R.drawable.ic_menu_mylocation)
        .setOngoing(true)
        .build()
    
    // Promotion in the foreground
    startForeground(
        service,
        notificationId,
        notification,
        ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION
    )
}

6. UserPreferencesRepository

Responsibility: Store user preferences for Danger Mode

Preferences structure

data class DangerModePreferences(
    val alertMode: Boolean,              // Enables/disables the system
    val inactivityDetection: Boolean,    // Enables the inactivity detection
    val automaticSms: Boolean,           // Automatically send SMS
    val automaticCalls: Boolean          // Automatically pass calls
)

Impact on behavior

alertMode inactivityDetection automaticSms automaticCalls Behavior
false * * * Inactive Danger Mode
true false * * Active Danger Mode, but no automatic detection
true true false false Active Detection, but no actions
true true true false SMS sending only
true true false true Call only
true true true true SMS + call

Usage scenarios

Scenario 1 : Complete automatic activation

Timeline:

T=0s    User enters hazardous area (earthquake zone)
        ├─→ StateManagerService detects Hazard
        ├─→ activeHazardFlow emits Hazard
        └─→ DangerModeService observes and automatically activates
            ├─→ isActive = true
            ├─→ activatingHazard = earthquake_hazard
            └─→ Capabilities configured based on permissions

T=30s   Earthquake occurs
        ├─→ Acceleration spike detected (65 m/s²)
        └─→ MovementService: SAFE → PRE_DANGER

T=31s   User immobile after fall
        ├─→ Low acceleration (0.8 m/s²)
        └─→ MovementService: PRE_DANGER → PRE_DANGER_ACC

T=31-40s Sample accumulation
        └─→ Values: [0.8, 0.7, 0.9, 0.6, 0.8, 0.7, 0.9, 0.8]

T=41s   Timeout reached, average = 0.775 < 1.0
        └─→ MovementService: PRE_DANGER_ACC → DANGER

T=41s   DangerModeOrchestrator evaluates the conditions
        ├─→ ✓ dangerState.isActive = true
        ├─→ ✓ activatingHazard != null
        ├─→ ✓ preferences.alertMode = true
        ├─→ ✓ preferences.inactivityDetection = true
        └─→ ✓ movementState = Danger
            └─→ TRIGGERING of the emergency protocol

T=41s   Emergency protocole
        ├─→ Retrieval of emergency contact : +41 79 123 45 67
        ├─→ Retrieval of GPS position : (46.2044, 6.1432)
        ├─→ Creating EmergencyMessage
        └─→ Determined action: SendSmsAndCall

T=42s  Voice confirmation (if enabled)
        ├─→ Confirmation screen display
        ├─→ Timeout: 30 seconds
        └─→ Waiting "Yes" or "No"

T=45s   User says "Yes" (or timeout)
        ├─→ Sending SMS: "🚨 EMERGENCY ALERT..."
        ├─→ Making a phone call
        └─→ lastActionTaken = Success("SMS and Call")

T=105s  Cooldown over (60s after action)
        └─→ State reset, ready for new triggering

Scenario 2 : Manual activation

T=0s    User manually enables the Danger Mode
        ├─→ DangerModeService.manualActivate()
        ├─→ isActive = true
        └─→ activatingHazard = null (key difference)

T=30s   User falls
        └─→ [Same detection process]

T=71s   Hazard detected in the zone
        └─→ NO automaticatic deactivation
            (because activatingHazard = null)

T=120s  User manually deactivate
        └─→ DangerModeService.manualDeactivate()

Scenario 3 : False alert

T=0s    Active Danger mode, user walks

T=30s   User stumbles slightly
        ├─→ Acceleration spike (52 m/s²)
        └─→ MovementService: SAFE → PRE_DANGER

T=31s   User catches up
        ├─→ Normal acceleration (3.5 m/s²)
        └─→ MovementService: PRE_DANGER → PRE_DANGER_ACC

T=31-40s Accumulation of normal walking samples
        └─→ Values: [3.5, 2.8, 3.2, 2.9, 3.1, 2.7, 3.4, 3.0]

T=41s   Timeout reached, average = 3.075 > 1.0
        └─→ MovementService: PRE_DANGER_ACC → SAFE
        └─→ NO urgent triggering

Scenario 4 : Voice confirmation refusal

T=0s-41s [Normal detection process]

T=41s   Emergency protocol triggered
        └─→ Voice confirmation screen display

T=45s   User says "No"
        ├─→ onCancellation(reportAsError = false)
        ├─→ lastActionTaken = Cancelled
        └─→ No emergency action executed

T=50s   State reset after delay (5s)
        └─→ Ready for renewed triggering if conditions persist

Scenario 5 : Contact error

T=0s-41s [Normal detection process]

T=41s   Emergency protocol triggered
        └─→ Retrieval of emergency contact: EMPTY

T=41s   Error detected
        ├─→ emergencyPhoneNumber.isBlank() = true
        ├─→ Log.w("No emergency phone number configured")
        ├─→ ErrorHandler.addError(ErrorType.NO_EMERGENCY_CONTACT)
        └─→ Protocol cancelled

[Required action: User must add an emergency contact]

Permissions management

Required permissions

Permission Usage Mandatory
SEND_SMS Send emergency SMS If automaticSms = true
CALL_PHONE Emergency calls If automaticCalls = true
ACCESS_FINE_LOCATION GPS position in message Yes
ACTIVITY_RECOGNITION Movement detection Yes
FOREGROUND_SERVICE GPS service in background Optional
FOREGROUND_SERVICE_LOCATION Type of foreground service If foreground GPS

Runtime validation

// When configuring capabilities
fun setCapabilities(capabilities: Set<DangerModeCapability>): Result<Unit> {
    for (cap in capabilities) {
        val requiredPermission = when (cap) {
            DangerModeCapability.SMS -> AppPermissions.SendEmergencySms
            DangerModeCapability.CALL -> null
        }
        
        requiredPermission?.let { perm ->
            val result = permissionManager.getPermissionResult(perm)
            if (result != PermissionResult.Granted) {
                return Result.failure(
                    IllegalStateException("Missing permission for ${cap.label}: ${perm.key}")
                )
            }
        }
    }
    
    _state.value = _state.value.copy(capabilities = validated)
    return Result.success(Unit)
}

Management of missing permissions

// In SmsSender
override fun sendSms(phoneNumber: String, message: EmergencyMessage) {
    require(phoneNumber.isNotBlank()) {
        "Phone number cannot be empty. Please add an emergency contact."
    }
    
    // SmsManager throws SecurityException if permission missing
    try {
        smsManager.sendTextMessage(phoneNumber, null, message.toStringMessage(), null, null)
    } catch (e: SecurityException) {
        //  Managed by ErrorHandler at the orchestrator level
        throw e
    }
}

// In CallSender
override fun placeCall(phoneNumber: String) {
    val granted = ContextCompat.checkSelfPermission(
        appContext,
        android.Manifest.permission.CALL_PHONE
    ) == PackageManager.PERMISSION_GRANTED
    
    if (!granted) {
        // Silent return, permission must be request before
        return
    }
    
    // Launch of the call
}

Error handling

Error types

enum class ErrorType {
    // GPS
    LOCATION_ERROR,
    LOCATION_NOT_GRANTED_ERROR,
    LOCATION_UPDATE_ERROR,
    FOREGROUND_ERROR,
    
    // Emergency
    NO_EMERGENCY_CONTACT,
    EMERGENCY_SMS_FAILED,
    EMERGENCY_CALL_FAILED,
    EMERGENCY_ACTION_CANCELLED,
    
    // Permissions
    PERMISSION_DENIED
}

ErrorHandler

class ErrorHandler {
    private val _errors = MutableStateFlow<Map<Screen, Set<ErrorType>>>(emptyMap())
    val errors: StateFlow<Map<Screen, Set<ErrorType>>> = _errors.asStateFlow()
    
    fun addErrorToScreen(type: ErrorType, screen: Screen) {
        _errors.update { current ->
            val screenErrors = current[screen].orEmpty() + type
            current + (screen to screenErrors)
        }
    }
    
    fun clearErrorFromScreen(type: ErrorType, screen: Screen) {
        _errors.update { current ->
            val screenErrors = current[screen].orEmpty() - type
            if (screenErrors.isEmpty()) {
                current - screen
            } else {
                current + (screen to screenErrors)
            }
        }
    }
}

Processing in the orchestrator

private fun executeEmergencyAction(action: PendingEmergencyAction) {
    try {
        when (action) {
            is PendingEmergencyAction.SendSms -> {
                smsSenderInstance?.sendSms(action.phoneNumber, action.message)
                _state.value = OrchestratorState(
                    lastActionTaken = EmergencyActionResult.Success("SMS")
                )
                // Cleaning up previous errors
                errorHandler.clearErrorFromScreen(ErrorType.EMERGENCY_SMS_FAILED, Screen.Dashboard)
                errorHandler.clearErrorFromScreen(ErrorType.NO_EMERGENCY_CONTACT, Screen.Dashboard)
            }
            // ... other cases
        }
    } catch (e: IllegalArgumentException) {
        // Invalid number = no configured contact
        errorHandler.addErrorToScreen(ErrorType.NO_EMERGENCY_CONTACT, Screen.Dashboard)
        _state.value = OrchestratorState(
            lastActionTaken = EmergencyActionResult.Failure(
                action::class.simpleName ?: "Unknown",
                e.message ?: "Invalid phone number"
            )
        )
    } catch (e: Exception) {
        // Failure of action 
        val errorType = when (action) {
            is PendingEmergencyAction.SendSms -> ErrorType.EMERGENCY_SMS_FAILED
            is PendingEmergencyAction.MakeCall -> ErrorType.EMERGENCY_CALL_FAILED
            is PendingEmergencyAction.SendSmsAndCall -> ErrorType.EMERGENCY_SMS_FAILED
        }
        errorHandler.addErrorToScreen(errorType, Screen.Dashboard)
        
        _state.value = OrchestratorState(
            lastActionTaken = EmergencyActionResult.Failure(
                action::class.simpleName ?: "Unknown",
                e.message ?: "Unknown error"
            )
        )
    }
    
    // 60-second cooldown before allowing a new trigger
    scope.launch {
        kotlinx.coroutines.delay(60000)
        resetState()
    }
}

Configuration and initialization

Initialization in MainActivity

private fun initializeDangerMode(isAuthenticated: Boolean) {
    if (isAuthenticated) {
        // Complete mode with synchronization
        val orchestrator = DangerModeOrchestrator()
        orchestrator.initialize(applicationContext)
        orchestrator.startMonitoring()
    } else {
        // Local mode only
        // Danger Mode disabled if not authenticated
    }
}

Orchestrator initialization

fun initialize(context: Context) {
    // Initialisation SMS sender
    if (smsSenderInstance == null) {
        smsSenderInstance = SmsManagerSender(context)
    }
    
    // Contacts repository initialization
    if (contactsRepo == null) {
        try {
            contactsRepo = ContactRepositoryProvider.repository
        } catch (e: Exception) {
            Log.w(TAG, "ContactsRepository not initialized yet")
        }
    }
    
    // Emergency number retrieval
    scope.launch {
        fetchEmergencyPhoneNumber()
        
        // Call sender initialization with number
        if (callSenderInstance == null) {
            callSenderInstance = CallIntentCaller(context, emergencyPhoneNumber)
        }
    }
}

private suspend fun fetchEmergencyPhoneNumber() {
    try {
        val contacts = contactsRepo?.getAllContacts()?.getOrNull()
        if (!contacts.isNullOrEmpty()) {
            emergencyPhoneNumber = contacts.first().phoneNumber
        } else {
            Log.w(TAG, "No emergency contacts found")
        }
    } catch (e: Exception) {
        Log.e(TAG, "Failed to fetch emergency contacts", e)
    }
}

Best practices

1. Always initialize before starting

// ❌ BAD
val orchestrator = DangerModeOrchestrator()
orchestrator.startMonitoring() // Crashes if context non initialized

// ✅ GOOD
val orchestrator = DangerModeOrchestrator()
orchestrator.initialize(applicationContext)
orchestrator.startMonitoring()

2. Clean-up resources

class DangerModeActivity : ComponentActivity() {
    private val orchestrator = DangerModeOrchestrator()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        orchestrator.initialize(applicationContext)
        orchestrator.startMonitoring()
    }
    
    override fun onDestroy() {
        super.onDestroy()
        orchestrator.stopMonitoring()
    }
}

3. Manage permission statuses

// In the ViewModel
fun onPermissionResult(permission: AppPermissions, granted: Boolean) {
    if (granted) {
        // Capabilities update
        val capabilities = mutableSetOf<DangerModeCapability>()
        if (permission == AppPermissions.SendEmergencySms) {
            capabilities.add(DangerModeCapability.SMS)
        }
        dangerModeService.setCapabilities(capabilities)
    } else {
        // Display error message to user
        _uiState.update {
            it.copy(errorMessage = "Permission ${permission.key} is required")
        }
    }
}

4. Log critical events

private fun executeEmergencyAction(action: PendingEmergencyAction) {
    Log.i(TAG, "Executing emergency action: ${action::class.simpleName}")
    
    try {
        // Execution
        Log.i(TAG, "Emergency action completed successfully")
    } catch (e: Exception) {
        Log.e(TAG, "Emergency action failed", e)
    }
}

5. Test with fakes, not with mocks

// ✅ GOOD : Controllable fake
class FakeDangerModeService : DangerModeService {
    private val _state = MutableStateFlow(DangerModeState())
    override val state: StateFlow<DangerModeState> = _state
    
    fun setState(newState: DangerModeState) {
        _state.value = newState
    }
}

// ❌ AVOID : Complex mock
val mockService = mock<DangerModeService>()
whenever(mockService.state).thenReturn(MutableStateFlow(DangerModeState()))

Complete sequence diagram

User    DangerMode  Movement  Orchestrator  GPS    SMS    Call
 │       Service    Service                 Svc    Svc    Svc
 │         │          │          │          │      │      │
 │ Enter   │          │          │          │      │      │
 │  zone   │          │          │          │      │      │
 ├────────>│          │          │          │      │      │
 │         │          │          │          │      │      │
 │      Hazard        │          │          │      │      │
 │    detected        │          │          │      │      │
 │         │          │          │          │      │      │
 │         │ Enable   │          │          │      │      │
 │         │ auto     │          │          │      │      │
 │         │          │          │          │      │      │
 │  Fall   │          │          │          │      │      │
 ├───────────────────>│          │          │      │      │
 │         │          │          │          │      │      │
 │         │       Detects       │          │      │      │
 │         │       inactivity    │          │      │      │
 │         │          │          │          │      │      │
 │         │          │  State = Danger     │      │      │
 │         │          ├─────────>│          │      │      │
 │         │          │          │          │      │      │
 │         │          │      Evaluates      │      │      │
 │         │          │      conditions     │      │      │
 │         │          │          │          │      │      │
 │         │          │      Get position   │      │      │
 │         │          │          ├─────────>│      │      │
 │         │          │          │<─────────┤      │      │
 │         │          │          │          │      │      │
 │         │          │      Get contact    │      │      │
 │         │          │      number         │      │      │
 │         │          │          │          │      │      │
 │         │          │      Send SMS       │      │      │
 │         │          │          ├───────────────> │      │
 │         │          │          │          │      │      │
 │         │          │      Place call     │      │      │
 │         │          │          ├───────────────────────>│
 │         │          │          │          │      │      │
 │<──────────────────────────────┴──────────┴──────┴──────┘
 │         │          │      Success        │      │      │

Summary

Warnastrophy's Danger Mode is a sophisticated system that:

  1. Automatically detects dangerous situations via:

    • Hazard zones
    • Motion sensors (falls, accidents)
  2. Coordinates several services:

    • DangerModeService for status
    • MovementService for detection
    • GPS for location
    • Contacts for emergency numbers
  3. Performs configurable actions:

    • Sends SMS messages with location
    • Makes emergency phone calls
    • Optional voice confirmation
  4. Robust error handling:

    • Missing permissions
    • Unconfigured contacts
    • Sending failures
  5. Respects privacy:

    • Manual activation/deactivation possible
    • Confirmation before action
    • User preferences respected

Resources

Clone this wiki locally