This document gives new contributors a fast, high-level map of how the Android application is organised: the major Java classes, resource folders, the startup / shutdown lifecycle, and the data + alarm flow.
OpenSeizureDetector is an Android foreground-service based application that:
- Collects motion (acceleration) and physiological (heart rate, optionally SpO₂) data from a wearable (Garmin, Pebble, BLE devices, phone sensors, etc.).
- Analyses incoming samples to detect tonic–clonic seizure patterns.
- Raises local (audible) and remote (SMS / phone call) alarms and optionally shares anonymised data with a central server.
- Provides a swipe-based main UI (MainActivity2 + fragments) and a startup checklist screen (StartupActivity) to ensure prerequisites are satisfied before normal operation.
Core runtime logic lives in the SdServer foreground service; Activities and Fragments mostly visualize status and manipulate preferences.
- User taps the launcher icon -> Android launches
StartupActivity(declared with MAIN/LAUNCHER intent filter inAndroidManifest.xml). StartupActivity:- Applies default preference values from XML (alarm, general, datasource, logging, etc.).
- Requests / validates required runtime permissions (notifications, SMS, location, Bluetooth, activity recognition, etc.).
- Starts (or restarts) the foreground service
SdServerviaOsdUtil.startServer()if not already running. - Binds to the service through
SdServiceConnectionto monitor status (watch connection, settings received, data flowing). - Displays a checklist (ProgressBars + TextViews) updated by a periodic timer until all conditions are OK.
- When all OK, transitions to either
MainActivity2(new UI) or legacyMainActivitydepending on theUseNewUipreference.
SdServer.onStartCommand():- Calls
updatePrefs(); selects concreteSdDataSource*implementation based onDataSourcepreference. - Instantiates and starts the chosen data source (e.g.,
SdDataSourceGarmin,SdDataSourceBLE,SdDataSourcePhone, etc.). - Initialises logging (
LogManager), location (LocationFinderif SMS alarms enabled), timers, embedded HTTP server (SdWebServer), wake lock, and notification channels; enters foreground (persistent notification). - Begins receiving data; populates
SdDataand runs analysis algorithms (e.g., seizure + heart rate) to update alarm state.
- Calls
MainActivity2binds to the already runningSdServerto present live status via Fragments.
There are several ways the service stops:
- User selects an "Exit" / stop option (menu action triggers
OsdUtil.stopServer()), which callsstopServiceforSdServer. - System kills the service (low memory or user force-stop) ->
SdServer.onDestroy()releases wake lock, stops data source, timers, web server, and cleans up. - Device reboot triggers
BootBroadcastReceiverwhich, ifAutoStartpreference is true, launchesStartupActivityto restart.
StartupActivity: Launcher activity; initial permission + readiness checklist; starts/binds the service; routes to main UI.MainActivity2: Modern, swipe-based interface usingViewPager2+ Fragments; shows system/algorithm status, data sharing, web server info, heart rate, ML algorithm, battery, watch signal etc.MainActivity: Legacy UI retained for backward compatibility (optionally used ifUseNewUiis false).PrefActivity: Preferences editor (headers + fragments defined inres/xml/*prefs.xml).BLEScanActivity: Discovers and selects BLE devices when using BLE/BLE2 data source.AuthenticateActivity: Handles login for data sharing / remote API.LogManagerControlActivity,ExportDataActivity,RemoteDbActivity: Data sharing, viewing, exporting, pruning local DB.ReportSeizureActivity,EditEventActivity: Manual event reporting / editing.
SdServer: Heart of the application. Manages:- Data source selection and life-cycle.
- Alarm evaluation (seizure, heart rate, fall, etc.).
- Notification updates (different channels for service status, events, data sharing issues).
- SMS & phone call alarm orchestration (timers to allow cancellation).
- Logging & data sharing upload scheduling.
- Embedded HTTP server (
SdWebServer) for local access. - Wake lock to keep CPU active during monitoring.
Each encapsulates a communication protocol + parsing logic:
SdDataSourcePebble– Legacy Pebble watch integration.SdDataSourceAw– Android Wear devices.SdDataSourceGarmin– Garmin watch app (acceleration + heart rate streams).SdDataSourceBLE/SdDataSourceBLE2– Generic BLE device integrations (v1 / v2 protocols for devices like PineTime / BangleJS).SdDataSourceNetwork– Pulls detector data over network from a remote instance.SdDataSourcePhone– Uses phone onboard sensors as the detector.
SdData: Aggregates current sample values, processing results, and metadata (data source name, versions, etc.).SdAlgHr: Heart rate alarm algorithms (simple threshold, adaptive, average-based).SdAlgNn: Neural network / machine learning based seizure detection (seeFragmentMlAlgfor UI).CircBuf: Circular buffer used for windowed averaging and historical metrics.
The principal seizure detection loop lives in the protected method SdDataSource.doAnalysis(), called each time a fresh window of accelerometer samples arrives (vector magnitude or derived from 3D data):
- Phone/watch battery percentages are updated and appended to rolling buffers.
- (Currently hard-coded) sample frequency set (e.g. 25 Hz) and frequency resolution derived from window length (
mNsamp). - FFT performed on the raw acceleration window using JTransforms
DoubleFFT_1D.realForward. - Overall spectrum "power" (
specPower) accumulated up to a cutoff frequency (FreqCutoff), zeroing higher bins to reduce noise. - Region of Interest (ROI) defined by preferences
AlarmFreqMin/AlarmFreqMax; average ROI power (roiPower) and the ratioroiRatio = 10 * roiPower / specPowercomputed. - Simplified spectrum (
simpleSpec[]) built in 1 Hz bins for UI visualisation (scaled byACCEL_SCALE_FACTORto align with historic Pebble scaling). - Populates fields in
mSdData(timestamps,specPower,roiPower, thresholds, ROI freq bounds, simplified spectrum array, flags) and markshaveData. - If the CNN alarm feature is enabled (
mCnnAlarmActive) thennnAnalysis()updatesmPseizureprobability. - Secondary narrow-band motion check via
flapCheck()(arm flapping detection) producing a boolean fed intoalarmCheck(). alarmCheck()applies thresholds (AlarmThresh,AlarmRatioThresh) and enabled algorithm flags (classic OSD, flap, CNN) to set alarm cause/state.- Additional modalities processed:
hrCheck()(heart rate alarms / frozen HR detection),o2SatCheck()(oxygen saturation),fallCheck()(fall detection), andmuteCheck()(user-induced mute logic). - Result dispatched upstream via
mSdDataReceiver.onSdDataReceived(mSdData)(SdServer consumes to raise notifications / alarms).
Key preferences influencing doAnalysis():
AlarmFreqMin/AlarmFreqMax: ROI frequency band.AlarmThresh/AlarmRatioThresh: Power and ratio thresholds for alarm state.- Flap detection thresholds (
FlapThresh,FlapRatioThresh, min/max flap band) when flap alarm active. - Flags enabling algorithms:
mOsdAlarmActive,mFlapAlarmActive,mCnnAlarmActive.
Performance / Extension Notes:
- Current implementation re-allocates FFT arrays each call; optimisation could reuse buffers.
- Sample frequency is hard-coded (25 Hz) inside analysis; aligning it with dynamic settings from watch would improve fidelity.
- Multiple ROIs could be generalised (current flapCheck duplicates spectral processing).
- Scaling (
ACCEL_SCALE_FACTOR) is applied post-hoc; future refactor could normalise early and adopt floating-point consistently for UI.
doAnalysis() is not called in a tight loop by the service; instead each concrete SdDataSource decides when a complete window of samples is ready and then invokes it:
- BLE (
SdDataSourceBLE/SdDataSourceBLE2): Acceleration notifications fill a raw buffer (rawDatalength 125 = 5s @25Hz). When full, the datasource copies buffered samples intomSdData, setsmNsamp, callsdoAnalysis(), then sends a one–byte alarm state back to the device via a status GATT characteristic. - Phone (
SdDataSourcePhone): Collects accelerometer sensor events, performs crude downsampling from ~50Hz to 25Hz. OncerawDatais full it triggersdoAnalysis(), resets counters and continues. - Pebble (
SdDataSourcePebble): The watch app performs analysis on-device and sends already processed results (includingalarmState, spectrum) – sodoAnalysis()is NOT used for Pebble; received data directly callsmSdDataReceiver.onSdDataReceived. - Network (
SdDataSourceNetwork): Fetches remote JSON; if successful passes parsedSdDataupward. Remote faults setalarmStateto NET FAULT (7). LocaldoAnalysis()not used. - Garmin (
SdDataSourceGarmin): Similar pattern (buffer fill ->doAnalysis()).
After doAnalysis() completes in a source that uses it:
- Spectral metrics (
roiPower,specPower, simplified spectrum) and timing fields are populated. flapCheck()optionally computes a narrow-band flap detection boolean.alarmCheck(flapDetected)applies power & ratio thresholds and accumulates time in alarm (mAlarmCount += mSamplePeriod) to transition through:- OK (0) -> WARNING (1) after
mWarnTimeseconds of continuous in-alarm condition. - WARNING (1) -> ALARM (2) after
mAlarmTimeseconds. - Recovery logic: leaving in-alarm state downgrades from ALARM (2) to WARNING (1) (simulating a just-entered warning), or from WARNING (1) to OK (0).
- OK (0) -> WARNING (1) after
- Other modality checks may elevate alarmState:
hrCheck(): If any heart rate alarm stands (simple / adaptive / average) setsalarmState = 2and appends cause tags (HR,HR_ADAPT,HR_AVG). Null HR may either cause alarm or fault depending onmHRNullAsAlarm.o2SatCheck(): Low or null oxygen saturation (with null-as-alarm enabled) sets standing flags and may escalate to ALARM.fallCheck(): SetsfallAlarmStandingand may signal FALL alarm state (3).muteCheck(): Watch/user mute setsalarmState = 6(MUTE) overriding other transient states.- Fault timers (
faultCheck()elsewhere) may set FAULT (4) or NET FAULT (7).
- The datasource calls
mSdDataReceiver.onSdDataReceived(mSdData)(implemented bySdServer).
| Code | Meaning | Origin / Trigger |
|---|---|---|
| 0 | OK | No current alarm condition or post-recovery. |
| 1 | WARNING | Thresholds exceeded for > warnTime but < alarmTime. |
| 2 | ALARM | Thresholds exceeded for > alarmTime, or HR/O₂/fall promoted, or HR adaptive/average thresholds stand. |
| 3 | FALL | Fall detection logic sets fallAlarmStanding or explicit fall state. |
| 4 | FAULT | Internal fault (e.g., missing data, HR sensor failure without null-as-alarm). |
| 5 | MANUAL ALARM | Raised manually (e.g., SdServer.raiseManualAlarm()). |
| 6 | MUTE | User/watch initiated mute; prevents audible alarm but maintains monitoring. |
| 7 | NET FAULT | Network datasource error / fault condition (SdDataSourceNetwork). |
SdServer.onSdDataReceived(sdData) interprets alarmState plus standing flags and performs side-effects:
- OK (0): Clears
alarmStandingunless latched (mLatchAlarms) from previous alarm or fall. - MUTE (6): Sets phrase "MUTE", suppresses alarms and notifications severity.
- WARNING (1): Plays warning tone (
warningBeep()), logs (if enabled), updates notification to warning channel/state. - ALARM (2) or MANUAL ALARM (5): Sets phrase "ALARM", raises
alarmStanding, plays alarm tone (alarmBeep()), shows main UI, posts high-severity notification, initiates latch timer (startLatchTimer()), and sends SMS / phone alarms if enabled (rate-limited to one per minute). - FALL (3 or
fallAlarmStandingtrue): Behaves similarly to ALARM but with phrase "FALL" (alarms + SMS sending). Fall may remain standing until cleared. - HR / O₂ / Adaptive HR / Average HR: These set
alarmState = 2when standing;alarmCauseaccumulates tokens; downstream handling identical to ALARM. - FAULT (4, 7, HR fault, frozen HR fault): Plays fault warning beep (
faultWarningBeep()), shows fault notification; may attempt datasource restart after timer (auto-restart currently disabled for BLE2 to prevent duplicate notifications).
With mLatchAlarms enabled, returning to OK does not immediately clear previous ALARM/FALL states; user must manually accept/reset (e.g., via UI actions) or wait for latch timer expiry (mLatchAlarmTimer). Without latching, state machine freely transitions downwards.
Upon each received dataset, SdServer updates internal mSdData, pushes it to SdWebServer for external viewing, and passes it to LogManager (mLm.updateSdData(mSdData)), which may create/append local events (especially on transitions into ALARM states) and schedule remote uploads.
BLE/BLE2 write a single-byte alarm state back to the watch/device after analysis (executeWriteCharacteristic(mStatusChar, statusVal) or peripheral write) enabling haptic / on-watch UI feedback.
Pebble handles its own alarm transitions internally before sending results.
This separation lets wearable implementations stay lightweight (simple streaming) while centralizing threshold timing, multi-modal fusion, and alarm escalation logic on the phone (except for Pebble legacy analysis).
FragmentCommon: Overall status & key indicators.FragmentOsdAlg: Seizure algorithm metrics (spectrum ratio, thresholds, raw/processed values).FragmentHrAlg: Heart rate algorithm status & thresholds.FragmentMlAlg: ML model results / confidence scores.FragmentBatt: Watch + phone battery status.FragmentSystem: System info (permissions, service state, logging flags).FragmentWatchSig: Signal quality / connectivity indicators.FragmentWebServer: Local web server URL / status.FragmentDataSharing: Data sharing setup state, counts of local vs remote events.
OsdUtil: Starts/stops/binds the service; permission checks; logging helpers; system/environment utilities.LogManager: Handles local + remote logging, event packaging, pruning, and upload scheduling.MlModelManager: Manages loading / inference of ML models (if in use).LocationFinder: Acquires GPS coordinates for SMS alarms.WebApiConnection/WebApiConnection_firebase/WebApiConnection_osdapi: Remote data sharing / API integrations.SdServiceConnection: Wraps service binding / connection callbacks, exposes convenience methods (watchConnected(),hasSdData(),hasSdSettings()).BootBroadcastReceiver: Auto-start on device boot when preference enabled.GattAttributes: BLE UUID constants and attribute names.OsdUncaughtExceptionHandler: Crash reporting path (uses UCE Handler library).SdWebServer: Lightweight embedded HTTP server (for local status / data access).
Under uk/org/openseizuredetector/data/...:
- Repository pattern for authentication:
LoginRepository,LoginDataSource,LoggedInUser,Result(standard wrapper around success/error).
res/
layout/ Activity & Fragment UI XML (e.g., startup_activity, activity_main2, fragment_*).
menu/ Action bar & overflow menus (e.g., main_activity_actions.xml).
values/ Strings (`strings.xml`), styles, colors, dimensions; base resources.
values-XX/ Localized strings (de, es, pl, ru, sl, sv, etc.).
drawable/ Icons and graphics (e.g., star_of_life_48x48). Might also include vector assets.
xml/ Preference definition files and network security config:
- alarm_prefs.xml
- basic_prefs.xml
- general_prefs.xml
- logging_prefs.xml
- pebble_datasource_prefs.xml
- network_datasource_prefs.xml
- network_passive_datasource_prefs.xml
- seizure_detector_prefs.xml
- preference_headers.xml (groups preferences)
- network_security_config.xml
Other notable folders:
assets/(if present) – Additional static assets (not heavily used here).libs/– Third-party JARs (e.g., FFT / chart libraries) bundled with the app.
- XML files under
res/xmldefine keys and defaults. StartupActivity.onCreate()callsPreferenceManager.setDefaultValues(...)for each preference file (once per install/version).- Classes such as
OsdUtil,SdServer,SdAlgHrinvokeupdatePrefs()to readSharedPreferences(PreferenceManager.getDefaultSharedPreferences(context)), caching operational parameters (thresholds, window lengths, flags). - Preference changes may trigger service restarts or algorithm behavior changes (e.g., enabling SMS alarms requires location permission and
LocationFinder).
Wearable / Phone Sensors --> Concrete SdDataSource --> SdServer (receives callbacks) -->
Algorithms (SdAlgNn, SdAlgHr, fall detection, etc.) --> Alarm State Transitions -->
Audible ToneGenerator / MP3 playback
Foreground Notification Updates
Timed SMS / Phone Call Alerts (with cancellation window)
Data Logging (local DB) / Remote Sharing (LogManager, WebApiConnection*)
Web Server exposure (SdWebServer)
Heart rate buffering uses CircBuf windows for simple/adaptive thresholding; seizure analysis (frequency spectrum, ratio thresholds) executed inside data source analysis routines (see respective SdDataSource* classes).
- Service runs in foreground with a persistent notification (required for stable long-running monitoring on modern Android).
- Wake lock prevents CPU sleep during monitoring sessions (battery intensive but improves reliability).
- Timers manage periodic tasks (event validation checks, remote upload scheduling, alarm muting windows).
- Boot auto-start via broadcast receiver ensures continuity if user opted in.
- Create a new
SdDataSource<YourDevice>class implementing the expected interface / callback pattern (see existing sources for template). - Handle connection, authentication/handshake, data parsing, and call back into
SdServerwith new samples. - Add a selection case in
SdServer.onStartCommand()for yourDataSourcepreference string. - Provide any additional preferences XML (e.g., update period, device address) and add them to default initialization in
StartupActivity. - Update UI fragments if device supplies new metrics.
- Service lifecycle & alarm orchestration:
SdServer.java(onStartCommand,onDestroy, timers, notifications). - Startup readiness checklist:
StartupActivity.serverStatusRunnable. - Data source selection:
SdServer.onStartCommand()switch overmSdDataSourceName. - Heart rate algorithms:
SdAlgHr.java. - ML / seizure algorithm UI:
FragmentMlAlg.java+SdAlgNn.java. - Permission checks:
StartupActivity&OsdUtil(Bluetooth, activity recognition, SMS, location). - Logging & data sharing:
LogManager.java,RemoteDbActivity.java,FragmentDataSharing.java. - Embedded web server logic:
SdWebServer.java.
| Preference Key | Purpose |
|---|---|
DataSource |
Selects which device source (Pebble, Garmin, BLE, Phone, Network). |
AlarmThresh / AlarmRatioThresh |
Seizure detection thresholds (spectrum amplitude / ratio). |
HRThreshMin / HRThreshMax |
Simple heart rate alarm bounds. |
HRAdaptiveAlarmWindowSecs |
Window size for adaptive HR average buffering. |
SMSAlarm / PhoneCallAlarm |
Enable remote alerts. |
LogData / LogDataRemote |
Enable local logging vs remote data sharing. |
UseNewUi |
Switch between legacy and modern main UI. |
AutoStart |
Auto-launch on device boot. |
(See res/xml/*_prefs.xml for full list.)
- Multiple channels for service status and events (IDs inside
SdServer). - Timers:
FaultTimer,CheckEventsTimer, SMS countdown (SmsTimer), latch alarm timer, etc., each controlling asynchronous transitions.
- User authenticates (
AuthenticateActivity) -> obtains token stored in preferences. LogManagerpackages events (timestamped, with retention pruning) and attempts periodic uploads (remoteLogPeriod).- Unvalidated remote events prompt UI reminders (
FragmentDataSharing).
UCEHandler integrated in Activities and Service to capture uncaught exceptions and offer sending logs via email.
SdWebServer exposes (read-only) status / logged data for local network access; started automatically by SdServer after data source initialisation.
- Stop:
OsdUtil.stopServer()-> callsstopService(Intent(SdServer)). - Start:
OsdUtil.startServer()->Context.startForegroundService(...)(on modern Android) then service builds notification & begins monitoring. - Restart triggered implicitly if critical permissions change (logic can call stop/start to reinitialise components).
To add new alarm logic (e.g., oxygen saturation):
- Introduce algorithm class (
SdAlgO2style) storing buffers and thresholds. - Update data source parsing to capture new metric.
- Integrate into
SdServerevaluation loop; amend notification text generation. - Provide preference keys + XML + UI Fragment display.
- "Latch Alarm": Alarm remains active until explicitly reset (even if underlying condition clears) for a configured duration.
- "Adaptive HR Alarm": Builds a moving average; raises alarm when HR deviates beyond +/- configurable delta.
- "Foreground Service": Long-lived component with persistent notification; less likely to be killed.
- "Data Sharing": User-consented upload of anonymised seizure events / sensor data to central server for algorithm improvement.
- Set breakpoints in
SdServer.onStartCommand()to inspect data source initialisation. - Use logs emitted via
OsdUtil.writeToSysLogFileto trace state transitions. - Inspect
StartupActivity.serverStatusRunnablefor readiness gating issues.
README.md: General project overview, build instructions.DEV_NOTES.txt: Developer notes / historical comments.BLE_Datasource_Specification.md: Protocol specifics for BLE devices.- PDFs under
doc/for algorithm assessment and app structure diagrams.
Below are two complementary diagrams (ASCII and Mermaid) showing how a data window travels from the wearable to an alarm being raised.
PNG Version: See FLOW_DIAGRAM.png in the repository root for a downloadable image.
ASCII Flow
Wearable Sensors (Accel / HR / O₂)
|
v
Watch Firmware / Device App
| (Pebble: does analysis + sends results)
| (BLE/Garmin/PineTime/etc.: streams raw samples)
v
SdDataSource (Buffer + Parse + Downsample/Scale)
| (Collect ~125 samples = 5s @25Hz)
v (window full)
doAnalysis()
|-> FFT (JTransforms) & Spectrum Power (specPower)
|-> ROI Power & Ratio (roiPower / roiRatio)
|-> Simplified Spectrum (simpleSpec[])
|-> flapCheck() narrow band detection
|-> nnAnalysis() (if CNN enabled)
|-> hrCheck(), o2SatCheck(), fallCheck(), muteCheck()
v
Populate mSdData (specPower, roiPower, simpleSpec, alarmState, alarmCause, metrics)
v
mSdDataReceiver.onSdDataReceived(mSdData)
v
SdServer.onSdDataReceived()
|-> Alarm state machine (OK/WARNING/ALARM/FALL/FAULT/MUTE)
|-> Latching logic (startLatchTimer if ALARM)
|-> Notifications (foreground + event channels)
|-> Tones (warningBeep / alarmBeep / faultWarningBeep)
|-> SMS / Phone Call (rate-limited, if enabled)
|-> Logging & Data Sharing (LogManager update, remote upload scheduling)
|-> Web Server update (SdWebServer.setSdData)
|-> Write alarmState byte back to device (BLE/Garmin)
v
UI Fragments / Web Server / Remote API Consumers
Mermaid Diagram (optional rendering if supported):
flowchart TD
W[Wearable Sensors\n(Accel / HR / O₂)] --> WF[Watch Firmware / Device App]
WF --> DS{SdDataSource\nBuffer & Parse}
DS -->|Window Full| AN[doAnalysis()]
AN --> FFT[FFT + Spectrum]
FFT --> ROI[ROI Power & Ratio]
ROI --> ALG[flapCheck / nnAnalysis / hrCheck / o2SatCheck / fallCheck / muteCheck]
ALG --> SD[SdData Populated\n(alarmState, metrics)]
SD --> RCV[mSdDataReceiver.onSdDataReceived]
RCV --> SRV[SdServer.onSdDataReceived]
SRV --> ACT[Alarm Actions\nNotification / Tone / SMS / Phone / Latch / Log]
SRV --> FEED[Write alarmState\nback to Device]
ACT --> UI[UI Fragments / Web Server / Data Sharing]
WF -. Pebble path (analysis on watch) .-> SD
Notes:
- Pebble path bypasses local
doAnalysis(); analysis executes on the watch and setsalarmStatebefore dispatch. - Network datasource substitutes "Buffer & Parse" with remote JSON fetch and may directly set NET FAULT (7) on failure.
- Latching prevents immediate clearing of ALARM/FALL states until user intervention or timer expiry.
README.md: General project overview, build instructions.DEV_NOTES.txt: Developer notes / historical comments.BLE_Datasource_Specification.md: Protocol specifics for BLE devices.- PDFs under
doc/for algorithm assessment and app structure diagrams.
Questions / Improvements: Feel free to open issues or pull requests on GitHub.