Repository navigation
Danger Mode Architecture
This page describes the complete architecture of Warnastrophy's Danger Mode, a core feature that automatically detects emergency situations and triggers appropriate safety actions.
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
┌─────────────────────────────────────────────────────────────────┐
│ DangerModeOrchestrator │
│ (Central coordination) │
└──────┬────────────┬────────────┬────────────┬────────────┬──────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Danger │ │Movement │ │ User │ │ GPS │ │ Contacts │
│ Mode │ │ Service │ │ Prefs │ │ Service │ │ Repo │
│ Service │ │ │ │ │ │ │ │ │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌──────────────────────────────────────────────────────────────────┐
│ Emergency Actions │
│ (SMS + Calls) │
└──────────────────────────────────────────────────────────────────┘
Responsibility: Managing the Danger Mode status
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
)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
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)
}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
Responsibility: Detection of suspicious inactivity via motion sensors
┌──────────┐
│ SAFE │
└────┬─────┘
│
│ Acceleration > preDangerThreshold
▼
┌─────────────┐
│ PRE_DANGER │ ← Acceleration spike detected
└──────┬──────┘
│
│ Acceleration ≤ dangerAverageThreshold
▼
┌──────────────────┐
│ PRE_DANGER_ACC │ ← Samples accumulation
└────────┬─────────┘
│
│ Timeout reached
▼
┌─────────┐
│ Evaluate│
│ average │
└────┬────┘
│
├─── < dangerAverageThreshold ──→ ┌────────┐
│ │ DANGER │
│ └────────┘
│
└─── ≥ dangerAverageThreshold ──→ ┌──────┐
│ SAFE │
└──────┘
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
}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)
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
)
)
}Responsibility: Central coordination and initiation of emergency actions
┌─────────────────────────────────────────────────────────┐
│ 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]
data class OrchestratorState(
val isWaitingForConfirmation: Boolean = false,
val pendingAction: PendingEmergencyAction? = null,
val confirmationTimeoutSeconds: Int = 0,
val lastActionTaken: EmergencyActionResult? = null
)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
)
}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)
}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()
}
}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)
}
}Responsibility: Provide GPS location for emergency messages
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")
}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
)
}Responsibility: Store user preferences for Danger Mode
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 |
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
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()
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
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
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]
| 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 |
// 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)
}// 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
}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
}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)
}
}
}
}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()
}
}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
}
}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)
}
}// ❌ BAD
val orchestrator = DangerModeOrchestrator()
orchestrator.startMonitoring() // Crashes if context non initialized
// ✅ GOOD
val orchestrator = DangerModeOrchestrator()
orchestrator.initialize(applicationContext)
orchestrator.startMonitoring()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()
}
}// 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")
}
}
}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)
}
}// ✅ 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()))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 │ │ │
Warnastrophy's Danger Mode is a sophisticated system that:
-
Automatically detects dangerous situations via:
- Hazard zones
- Motion sensors (falls, accidents)
-
Coordinates several services:
- DangerModeService for status
- MovementService for detection
- GPS for location
- Contacts for emergency numbers
-
Performs configurable actions:
- Sends SMS messages with location
- Makes emergency phone calls
- Optional voice confirmation
-
Robust error handling:
- Missing permissions
- Unconfigured contacts
- Sending failures
-
Respects privacy:
- Manual activation/deactivation possible
- Confirmation before action
- User preferences respected