Skip to content

ElsevierSoftwareX/SOFTX-D-26-00021

 
 

Repository files navigation

E-Sentinel

SOS Emergency Alert Application

E-Sentinel is an SOS emergency alert application designed to respond to voice commands during critical situations such as accidents, war zones, or natural disasters. It works entirely offline (SOS support) for voice recognition using local language models, while still providing real-time location tracking and automated WhatsApp/SMS alerts.


Table of Contents


Key Features

  • Offline Voice Command Recognition
    Detects emergency phrases like “call doctor”, “help me”, or “emergency situation” using local Vosk speech models—no internet required for speech recognition.

  • Real-time Location Tracking
    Finds and maps the nearest hospitals or safe zones using Google Maps APIs.

  • Twilio WhatsApp & SMS Integration
    Sends instant alerts containing the user’s live location to emergency contacts.

  • Multiple SOS Triggers

    • Voice command
    • Neutrosophic Fall detection
    • Double-tap (volume button)
    • Manual SOS button
  • Fall Detection System
    Uses neutrosophic logic to make fall detection robust under noisy and ambiguous motion patterns.

  • Multilingual Support
    Recognizes multiple languages — English, Hindi, and Telugu — through offline models.

  • Customizable Emergency Contacts
    Update your mobile numbers in the app before use to ensure alerts reach the correct people.


Setup & Installation

The repository includes a minimal working example demonstrating installation and SOS activation.

1. Clone the Repository

git clone https://github.com/E-Sentinel-Project/E-Sentinel.git
cd E-Sentinel

2. Update API Keys & Phone Numbers

Open the app-level Gradle file:

app/build.gradle.kts

Locate the buildConfigField entries and replace the placeholder values with your actual API keys and phone numbers:

buildConfigField("String", "GOOGLE_MAPS_API_KEY", "\"YOUR_API_KEY\"")
buildConfigField("String", "NEWS_API_KEY", "\"YOUR_API_KEY\"")
buildConfigField("String", "TWILIO_ACCOUNT_SID", "\"YOUR_API_KEY\"")
buildConfigField("String", "TWILIO_AUTH_TOKEN", "\"YOUR_API_KEY\"")
buildConfigField("String", "OPEN_WEATHER_MAP_API_KEY", "\"YOUR_API_KEY\"")
buildConfigField("String", "TWILIO_SMS_NUMBER", "\"YOUR_TWILIO_PHONE_NUMBER\"")
buildConfigField("String", "TWILIO_WHATSAPP_NUMBER", "\"whatsapp:TWILIO_WHATSAPP_NUMBER\"")
buildConfigField("String", "ALERT_PHONE_NUMBER", "\"YOUR_ALERT_PHONE_NUMBER\"")
buildConfigField("String", "Whatsapp_ALERT_PHONE_NUMBER", "\"whatsapp:YOUR_WHATSAPP_NUMBER\"") 

Important Notes:

  • Replace all YOUR_API_KEY values with valid keys from the respective services.
  • Update TWILIO_SMS_NUMBER with your Twilio-registered number.
  • Update ALERT_PHONE_NUMBER with the emergency contact number that should receive alerts.
  • These values are compiled into BuildConfig and accessed securely at runtime.
  • To send SOS via Twilio Whatsapp write contact number after a prefix of "whatsapp:".

3. Add Offline Voice Models

Download Vosk models from: https://alphacephei.com/vosk/models

Extract the models into:

models/src/main/assets/

Example:

models/src/main/assets/model-small-en-in

4. Map the Model in MainActivity.kt

private val modelMap = mapOf(
    "English India (Small)" to "model-small-en-in",
    "Hindi (Small)" to "model-small-hi",
    "Telugu (Small)" to "model-small-te"
)

Note: These voice models run entirely offline on the device, ensuring reliable emergency voice recognition even without internet connectivity.


How It Works

  1. User triggers SOS by:

    • Saying a distress phrase
    • Falling (detected via accelerometer)
    • Pressing volume buttons twice
    • Pressing the SOS button
  2. App fetches user location via FusedLocationProviderClient.

  3. Alert is sent via Twilio WhatsApp or SMS API with:

    • User’s live location
    • Preset emergency message
    • Custom emergency contacts (update mobile numbers in the code before use)
  4. Optional features:

    • Shortest route to nearest hospital
    • Local news and weather data

Collection Protocol

The dataset (fall_testing.csv) is fully reproducible, anyone can generate their own dataset through logs and validate by the colab notebook (fall_testing.ipynb).

Our code generates 2 internal files which can be accessed through Android Studio:

  1. data/data/com.example.e_sentinel/files/falls_dataset_clean.csv (when system predicts fall)

From this file tester can compute:

Case Condition Confusion Matrix
System = TRUE, ActualFall = TRUE User confirms fall TP
System = TRUE, ActualFall = FALSE User dismisses alert FP

and

  1. data/data/com.example.e_sentinel/files/fall_stream_log.csv (displays all logs->Used to test when system does not predict fall)

From this file tester can compute:

Case Condition Confusion Matrix
System = FALSE, ActualFall = TRUE Fall occurred but no alert FN
System = FALSE, ActualFall = FALSE No fall and no alert TN

Copy paste log values from these files to an empty CSV file to create dataset.

The tester fills Activity (fall, run, jump, fast walk, lying down intentionally, etc.) and ActualFall (True/False).

To evaluate metrics and generate confusion matrix, tester can upload their own generated dataset to our colab notebook and obtain the results as per their dataset.


Sample Code Snippets Analysis

This section presents representative code snippets that demonstrate how the core safety-critical logic of the system operates.

The complete project source code (including UI, networking, and platform-specific components) is available in the GitHub repository. The examples shown here are intentionally simplified to improve readability and focus on decision logic rather than concurrency, lifecycle management, or error handling.


Neutrosophic Fall Detection

The neutrosophic fall detection logic is implemented as a standalone, offline routine that processes real-time accelerometer data. It does not require model training, runs continuously on mobile devices, and is optimized for low power consumption.

The algorithm uses three compact motion features:

  • Filtered acceleration magnitude
  • Jerk (rate of change of acceleration)
  • Orientation angle

These features are mapped to neutrosophic values:

  • Truth (T) – evidence supporting a fall
  • Indeterminacy (I) – uncertainty from ambiguous motion
  • Falsity (F) – evidence supporting non-fall activity

Feature Normalization Ranges

Feature Range Purpose
Acceleration magnitude 15–30 m/s² Detect impact impulse
Jerk 10–35 m/s³ Detect abrupt motion
Orientation angle 35°–85° Detect abnormal posture

These thresholds were derived empirically through pilot testing on fall and non-fall activities.

Design Characteristics

  • Soft decision boundaries (not hard thresholds)

  • Weighted evidence combination:

    • Acceleration: 0.55
    • Jerk: 0.30
    • Orientation: 0.15
  • Indeterminacy capped at 0.30

  • Falsity enforced at a minimum of 0.10

  • Fully configurable parameters

A fall is suspected only when Truth significantly exceeds both Indeterminacy and Falsity, reducing false alarms.

Formally, a fall is triggered when:

(T − F > I) AND (T > 0.6)

Core Detection Logic

private fun detectFallNeutrosophic(
    filteredAccel: Float,
    jerk: Float,
    angle: Float
): Boolean {

    // Truth (T): evidence supporting a fall
    val T = (
            normalize(filteredAccel, 15f, 30f) * 0.55f +
                    normalize(jerk, 10f, 35f) * 0.30f +
                    normalize(angle, 35f, 85f) * 0.15f
            ).coerceIn(0f, 1f)

    // Indeterminacy (I): uncertainty from ambiguous motion
    val I = (
            normalize(jerk, 3f, 12f) * 0.4f +
                    normalize(angle, 10f, 35f) * 0.6f
            ).coerceIn(0f, 0.3f)

    // Falsity (F): strong evidence of non-fall activity
    val F = (
            normalizeStableGravity(filteredAccel) * 0.6f +
                    normalizeSmallJerk(jerk) * 0.4f
            ).coerceIn(0.1f, 1f)

    // Fall-suspected decision
    return (!fallDetected && (T - F > I) && T > 0.6f)

Motion Feature Extraction

Raw accelerometer data is transformed into compact motion descriptors using a lightweight preprocessing pipeline:

  • Kalman-filtered acceleration magnitude
  • Jerk (finite difference with timestamps)
  • Orientation angle from gravity vector

The inverse cosine output is converted from radians to degrees before normalization.

This module is designed for real-time execution with minimal computational overhead.

// Previous accelerometer state
private var lastAcc = FloatArray(3) { 0f }
private var lastTs = 0L
private val kFilter = Kalman1D()
private fun extractMotionFeatures(event: SensorEvent): FloatArray {
    val x = event.values[0]
    val y = event.values[1]
    val z = event.values[2]

    val rawAccel = Math.sqrt( // Raw acceleration magnitude
        (x * x + y * y + z * z).toDouble()
    ).toFloat()
    // Kalman-filtered acceleration
    val filteredAccel = kFilter.update(rawAccel)

    val timestamp = event.timestamp // Time difference
    val dt = if (lastTs == 0L) 0.0 else
        (timestamp - lastTs) / 1_000_000_000.0
    // Jerk computation
    val jerk = if (dt > 0) {
        val diff = Math.sqrt(
            ((x - lastAcc[0]) * (x - lastAcc[0]) +
                    (y - lastAcc[1]) * (y - lastAcc[1]) +
                    (z - lastAcc[2]) * (z - lastAcc[2])).toDouble()
        ).toFloat()
        diff / dt.toFloat()
    } else 0f

    val angle = Math.toDegrees( // Orientation angle
        Math.acos((z / filteredAccel).coerceIn(-1f, 1f).toDouble())
    ).toFloat()

    // Update state
    lastTs = timestamp
    lastAcc[0] = x; lastAcc[1] = y; lastAcc[2] = z

    return floatArrayOf(filteredAccel, jerk, angle)
}

Keyword-Based SOS Trigger

The system includes an offline, speech-driven SOS mechanism using on-device speech recognition.

Key Characteristics

  • Works without internet connectivity
  • Lightweight keyword matching
  • Human-in-the-loop confirmation
  • Tolerant to recognition noise

Distress Keyword Registry

val distressKeywords = listOf(
    "help", "help me", "save me", "emergency", "please help",
    "i need help", "i'm in danger", "i am in danger",
    "call the police", "sos", "attack", "kidnap",
    "rape", "fire", "someone is following me",
    "danger", "please rescue me"
)

Speech Recognition Callback

// Vosk Listener Callback
override fun onResult(hypothesis: String) {
        runOnUiThread {
            try {
                val text = JSONObject(hypothesis).optString("text");
                if (text.isNotEmpty()) {

                    finalView.text = text;
                    val spokenText = text.lowercase()

                    val distressDetected = distressKeywords.any { keyword ->
                        spokenText.contains(keyword)
                    }

                    if (distressDetected) {
                        runOnUiThread {

                            getLocation { lat, lon ->
                                startAutoSOSCountdown(lat, lon)

                                AlertDialog.Builder(this@MainActivity)
                                    .setTitle("Voice-Based SOS Detected")
                                    .setMessage(
                                        "You said something that sounds like a distress call.\n\n" +
                                                "SOS will be sent automatically in 10 seconds if no response."
                                    )
                                    .setPositiveButton("Yes, Send SOS") { _, _ ->
                                        cancelAutoSOSCountdown()

                                        sendSOS(lat, lon)
                                        Toast.makeText(
                                            this@MainActivity,
                                            " SOS Triggered via Voice Command!",
                                            Toast.LENGTH_LONG
                                        ).show()
                                    }
                                    .setNegativeButton("No, I'm Fine") { _, _ ->
                                        cancelAutoSOSCountdown()

                                        fallDetected = false
                                    }
                                    .setCancelable(false)
                                    .show()
                            }
                        }
                    }
                }

            } catch (e: Exception) {
                finalView.text = hypothesis;
            }
        }
    }

SOS Alert Dispatch

Once an emergency is confirmed, the system sends an SOS alert containing a Google Maps location link using the Twilio SMS API.

Design Features

  • HTTPS-based secure communication
  • Background-thread execution
  • Modular dispatch independent of trigger source
  • Immediate user feedback
private fun sendSOS(latitude: Double?, longitude: Double?) {
        if (!isInternetAvailable()) {
            openSmsAppWithMessage(latitude, longitude)
            Toast.makeText(
                this,
                "No internet. Opening SMS app for offline SOS.",
                Toast.LENGTH_LONG
            ).show()
            return
        }
        Thread {
            try {
                val sid = BuildConfig.TWILIO_ACCOUNT_SID
                val auth = BuildConfig.TWILIO_AUTH_TOKEN
                val fromPhone = BuildConfig.TWILIO_SMS_NUMBER
                val toPhone = BuildConfig.ALERT_PHONE_NUMBER
                val message = if (latitude != null && longitude != null) {
                    "🚨 SOS Alert! Location: https://maps.google.com/?q=$latitude,$longitude"
                } else {
                    "🚨 SOS Alert! Location unavailable."
                }

                val formBody = FormBody.Builder()
                    .add("From", fromPhone)
                    .add("To", toPhone)
                    .add("Body", message)
                    .build()

                val request = Request.Builder()
                    .url("https://api.twilio.com/2010-04-01/Accounts/$sid/Messages.json")
                    .post(formBody)
                    .header("Authorization", Credentials.basic(sid, auth))
                    .build()

                val response = client.newCall(request).execute()
                runOnUiThread {
                    if (response.isSuccessful) {
                        Toast.makeText(this, "SOS Sent!", Toast.LENGTH_LONG).show()
                    } else {
                        Toast.makeText(
                            this,
                            "Error: ${response.message}",
                            Toast.LENGTH_SHORT
                        ).show()
                    }
                }
            } catch (e: Exception) {
                runOnUiThread {
                    Toast.makeText(this, "Error sending SOS", Toast.LENGTH_SHORT).show()
                }
            }
        }.start()
    }

Volume Button–Based SOS Trigger

This feature lets the user trigger an SOS alert by double-pressing either the Volume Up or Volume Down button. It is designed for situations where touching the screen or using voice commands is not possible.

The app listens for volume button key events and checks the time between two presses. If the second press happens within a fixed interval, it is treated as an intentional SOS action. This helps avoid accidental triggers during normal volume adjustment.

When the SOS is activated, the app fetches the user’s current location and sends it using the existing SOS dispatch logic. The key event is consumed so the system volume does not change, and a short on-screen message confirms that the SOS was triggered.


Configuration Parameters

private var lastVolumeButtonTime = 0L
private val DOUBLE_PRESS_INTERVAL = 800 // milliseconds
Parameter Description
DOUBLE_PRESS_INTERVAL Maximum allowed time between presses
lastVolumeButtonTime Tracks timing of previous button press

The interval can be adjusted to balance responsiveness vs. false triggers.


override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
    val currentTime = System.currentTimeMillis()

    if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
        if (currentTime - lastVolumeButtonTime <= DOUBLE_PRESS_INTERVAL) {
            lastVolumeButtonTime = 0L

            getLocation { lat, lon ->
                sendSOS(lat, lon)
            }

            Toast.makeText(this, "SOS Activated by Volume Button!", Toast.LENGTH_SHORT).show()
        } else {
            lastVolumeButtonTime = currentTime
        }

        return true
    }

    return super.onKeyDown(keyCode, event)
}

Medical Emergency Navigation

This module launches native map navigation to nearby hospitals using a geo-URI.

//fetch the 'btnMaps' button from XML
val btnMaps: Button = findViewById(R.id.btnMaps)
btnMaps.setOnClickListener {
    // Construct URI (Uniform Resource Identifier) to search for nearby hospitals
    val gmmIntentUri = Uri.parse("geo:0,0?q=hospital")
    val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri)

    // Open Google Maps with the requested URI
    mapIntent.setPackage("com.google.android.apps.maps")
    startActivity(mapIntent)
}

Traffic-Aware Route Calculation

Uses the Google Directions API with real-time traffic parameters:

  • departure_time=now
  • traffic_model=best_guess

Returns:

  • Distance
  • Travel duration (traffic-adjusted)
  • Start and end addresses
// Roads Button -> Shortest Path via Directions API
// Roads Button -> Optimal Route with traffic
val btnRoads: Button = findViewById(R.id.btnRoads)
btnRoads.setOnClickListener {
    getLocation { lat, lon ->
        // Ask user for destination
        val input = EditText(this)
        AlertDialog.Builder(this)
            .setTitle("Enter Destination Address")
            .setView(input)
            .setPositiveButton("Go") { _, _ ->
                val destination = input.text.toString().trim()
                if (destination.isNotEmpty()) {
                    Thread {
                        try {
                            val apiKey = BuildConfig.GOOGLE_MAPS_API_KEY
                            // Directions API URL with traffic info
                            val url =
                                "https://maps.googleapis.com/maps/api/directions/json?" +
                                        "origin=$lat,$lon&destination=${Uri.encode(destination)}" +
                                        "&mode=driving&departure_time=now&traffic_model=best_guess&key=$apiKey"

                            val request = Request.Builder().url(url).build()
                            val response = OkHttpClient().newCall(request).execute()
                            val json = JSONObject(response.body?.string() ?: "{}")

                            val routes = json.optJSONArray("routes")
                            if (routes != null && routes.length() > 0) {
                                val legs = routes.getJSONObject(0).getJSONArray("legs")
                                val leg = legs.getJSONObject(0)
                                val distance = leg.getJSONObject("distance").getString("text")
                                val duration = leg.optJSONObject("duration_in_traffic")?.getString("text")
                                    ?: leg.getJSONObject("duration").getString("text")
                                val startAddress = leg.getString("start_address")
                                val endAddress = leg.getString("end_address")

                                runOnUiThread {
                                    AlertDialog.Builder(this)
                                        .setTitle("Optimal Route")
                                        .setMessage(
                                            "From: $startAddress\n" +
                                            "To: $endAddress\n" +
                                            "Distance: $distance\n" +
                                            "Estimated Time: $duration"
                                        )
                                        .setPositiveButton("Open in Maps") { _, _ ->
                                            val gmmIntentUri =
                                                Uri.parse(
                                                    "https://www.google.com/maps/dir/?api=1&origin=$lat,$lon&destination=${Uri.encode(destination)}&travelmode=driving"
                                                )
                                            val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri)
                                            mapIntent.setPackage("com.google.android.apps.maps")
                                            startActivity(mapIntent)
                                        }
                                        .setNegativeButton("Close", null)
                                        .show()
                                }
                            } else {
                                runOnUiThread {
                                    Toast.makeText(this, "No route found", Toast.LENGTH_SHORT).show()
                                }
                            }
                        } catch (e: Exception) {
                            runOnUiThread {
                                Toast.makeText(this, "Error fetching route", Toast.LENGTH_SHORT).show()
                            }
                        }
                    }.start()
                } else {
                    Toast.makeText(this, "Destination cannot be empty", Toast.LENGTH_SHORT).show()
                }
            }
            .setNegativeButton("Cancel", null)
            .show()
    }
}

Location-Based News Aggregation

Delivers geographically relevant news using:

  1. Reverse geocoding (lat/lon → city)
  2. Location-based news query

Includes background-thread execution.

// News Button -> Local News
val btnNews: Button = findViewById(R.id.btnNews)
btnNews.setOnClickListener {
    getLocation { lat, lon ->
        // Step 1: Get nearest city name
        Thread {
            try {
                val geoApiKey = BuildConfig.GOOGLE_MAPS_API_KEY
                val geoUrl = "https://maps.googleapis.com/maps/api/geocode/json?latlng=$lat,$lon&key=$geoApiKey"

                val geoRequest = Request.Builder().url(geoUrl).build()
                val geoResponse = OkHttpClient().newCall(geoRequest).execute()
                val geoJson = JSONObject(geoResponse.body?.string() ?: "{}")
                val results = geoJson.optJSONArray("results")

                var cityName: String? = null
                if (results != null && results.length() > 0) {
                    val addressComponents = results.getJSONObject(0).getJSONArray("address_components")
                    for (i in 0 until addressComponents.length()) {
                        val component = addressComponents.getJSONObject(i)
                        val types = component.getJSONArray("types")
                        for (j in 0 until types.length()) {
                            if (types.getString(j) == "locality") {
                                cityName = component.getString("long_name")
                                break
                            }
                        }
                    }
                }

                // Step 2: Fetch local news using NewsData API
                val newsApiKey = BuildConfig.NEWS_API_KEY
                val newsUrl = "https://newsdata.io/api/1/news?apikey=$newsApiKey&country=in&language=en&q=$cityName"

                val newsRequest = Request.Builder().url(newsUrl).build()
                val newsResponse = OkHttpClient().newCall(newsRequest).execute()
                val newsJson = JSONObject(newsResponse.body?.string() ?: "{}")
                val articles = newsJson.optJSONArray("results")

                runOnUiThread {
                    if (articles != null && articles.length() > 0) {
                        val firstArticle = articles.getJSONObject(0)
                        val title = firstArticle.optString("title", "No Title")
                        val description = firstArticle.optString("description", "No Description Available")
                        val link = firstArticle.optString("link", "")

                        AlertDialog.Builder(this)
                            .setTitle("Local News ($cityName)")
                            .setMessage("$title\n\n$description")
                            .setPositiveButton("Read More") { _, _ ->
                                if (link.isNotEmpty()) {
                                    val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
                                    startActivity(intent)
                                }
                            }
                            .setNegativeButton("Close", null)
                            .show()
                    } else {
                        Toast.makeText(this, "No local news found for $cityName", Toast.LENGTH_SHORT).show()
                    }
                }

            } catch (e: Exception) {
                runOnUiThread {
                    Toast.makeText(this, "Error fetching news", Toast.LENGTH_SHORT).show()
                }
            }
        }.start()
    }
}

Local Weather Monitoring

Fetches real-time weather using the user's current geographic coordinates.

val btnWeather: Button = findViewById(R.id.btnWeather)
btnWeather.setOnClickListener {
    getLocation { lat, lon ->
        if (lat != null && lon != null) {
            fetchLocalWeather(lat, lon)
        } else {
            Toast.makeText(this, "Location unavailable.", Toast.LENGTH_SHORT).show()
        }
    }
}

private fun fetchLocalWeather(latitude: Double, longitude: Double) {
    Thread {
        try {
            val apiKey = BuildConfig.OPEN_WEATHER_MAP_API_KEY
            val url =
                "https://api.openweathermap.org/data/2.5/weather?lat=$latitude&lon=$longitude&units=metric&appid=$apiKey"

            val request = Request.Builder().url(url).build()
            val response = OkHttpClient().newCall(request).execute()
            val json = JSONObject(response.body?.string() ?: "{}")

            val weatherArray = json.optJSONArray("weather")
            val mainObject = json.optJSONObject("main")

            val description = weatherArray?.getJSONObject(0)?.getString("description") ?: "N/A"
            val temp = mainObject?.getDouble("temp") ?: 0.0

            runOnUiThread {
                AlertDialog.Builder(this)
                    .setTitle("Local Weather")
                    .setMessage("🌡️ Temperature: $temp °C\n🌥️ Condition: $description")
                    .setPositiveButton("OK", null)
                    .show()
            }
        } catch (e: Exception) {
            runOnUiThread {
                Toast.makeText(this, "Error fetching weather info", Toast.LENGTH_SHORT).show()
            }
        }
    }.start()
}

Offline-SOS Messages

The SOS module is the most comprehensive component of the E-Sentinel framework, integrating multiple emergency-trigger mechanisms, including:

  1. SendSOS button
  2. Neutrosophic logic-based Fall detection
  3. double-tap SOS
  4. SOS initiation through distress keywords

To ensure reliable operation under varying connectivity conditions, a dual communication strategy is employed. When internet access is available, SOS alerts are transmitted through the Twilio cloud service. In offline environments, the system seamlessly switches to the Android native SMS interface, where a pre-filled emergency message is automatically prepared for the designated contact, requiring only a single user action to send. This dual-mode design minimizes alert latency and enhances system reliability, which is particularly critical in time-sensitive emergency scenarios where even minor delays can significantly affect outcomes.

From a security and societal perspective, preventing in-app offline APK redistribution reduces the risk of malware propagation and unauthorized software cloning. Android's security model enforces this by disallowing runtime rebuilding or re-signing of APKs, which are compile-time operations. This sandboxing mechanism blocks self-modifying or self-replicating applications, ensuring that emergency applications like E-Sentinel cannot be misused as malware carriers. As a result, offline APK transfer is limited to trusted pre-built distribution channels, such as desktop-to-device sharing or APK extractor tools that copy already signed applications without modification.

private fun openSmsAppWithMessage(latitude: Double?, longitude: Double?) {
    val phoneNumber = BuildConfig.ALERT_PHONE_NUMBER
    val message =
        "SOS ALERT!\n" +
                "Immediate help needed.\n" +
                if (latitude != null && longitude != null) {
                    "Location: https://maps.google.com/?q=$latitude,$longitude"
                } else {
                    "Location unavailable."
                }

    val intent = Intent(Intent.ACTION_SENDTO).apply {
        data = Uri.parse("smsto:$phoneNumber")
        putExtra("sms_body", message)
    }

    try {
        startActivity(intent)
    } catch (e: Exception) {
        Toast.makeText(this, "No SMS app available", Toast.LENGTH_SHORT).show()
    }
}

Automatic-SOS Messages

For both the neutrosophic fall detection and keyword-based SOS modules, an automatic SOS triggering mechanism is implemented. Once an SOS is initiated, the system waits for 10 seconds for user confirmation. If no response is received within this interval, the SOS alert is automatically dispatched. This design is especially critical in emergency scenarios where the user may be incapacitated, unconscious, or severely injured and therefore unable to manually confirm or cancel the alert. The automatic triggering ensures timely assistance without relying on user intervention.

private fun startAutoSOSCountdown(latitude: Double?, longitude: Double?) {
    sosRunnable = Runnable {
        sendSOS(latitude, longitude)
        Toast.makeText(
            this,
            "No response detected. SOS sent automatically!",
            Toast.LENGTH_LONG
        ).show()
    }
    sosHandler.postDelayed(sosRunnable!!, 10_000) // 10 seconds
}

private fun cancelAutoSOSCountdown() {
    sosRunnable?.let { sosHandler.removeCallbacks(it) }
}

Tech Stack

  • Android (Kotlin)
  • Vosk Offline Speech Recognition
  • Google Maps & Directions APIs
  • Twilio API (SMS/WhatsApp)
  • OpenWeather API
  • NewsData.io API

Pending Works

  • Settings page to edit mobile numbers (For now the mobile numbers are hard-coded)
  • Running the VOSK model as a background service

Contributors

Developed by Harsh Patel, B. Jaison Edward and Dr. Ilanthenral Kandasamy.

For contributions or issues — please open a Pull Request or Issue.


E-SentinelEmergency response that works offline (for SOS alerts) and in real-time.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Jupyter Notebook 91.2%
  • Kotlin 8.8%