Minor changes to seizure report

This commit is contained in:
2026-05-13 10:18:21 +00:00
parent f1bd6eb4f9
commit 1bcd601786
4 changed files with 455 additions and 169 deletions
+4 -4
View File
@@ -43,7 +43,7 @@
android:required="false" /> android:required="false" />
<application <application
android:icon="@drawable/star_of_life_48x48" android:icon="@drawable/floga_app_icon"
android:label="@string/app_name" android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
@@ -62,17 +62,17 @@
<activity android:name=".ExportDataActivity" /> <!-- android:usesCleartextTraffic="true" --> <activity android:name=".ExportDataActivity" /> <!-- android:usesCleartextTraffic="true" -->
<activity <activity
android:name=".StartupActivity" android:name=".StartupActivity"
android:exported="true"> android:exported="true"
android:icon="@drawable/floga_app_icon">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:icon="@drawable/star_of_life_48x48" android:icon="@drawable/floga_app_icon"
android:label="@string/app_name" /> android:label="@string/app_name" />
<activity <activity
android:name=".PrefActivity" android:name=".PrefActivity"
@@ -10,27 +10,33 @@ import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
public class ReportManager { public class ReportManager {
private static final String TAG = "ReportManager"; private static final String TAG = "ReportManager";
private static final int GROUP_THRESHOLD_SECS = 30; private static final int FALLBACK_GROUP_THRESHOLD_SECS = 30;
private static final int MONTHS_TO_SHOW = 12; private static final int SAME_SEIZURE_START_TOLERANCE_SECS = 45;
private static final int DEFAULT_MARKER_DURATION_SECS = 5 * 60;
private static final int MAX_TIMELINE_DURATION_SECS = 15 * 60;
private static final boolean ADD_DEMO_EVENTS = true;
private static final int DEMO_EVENT_COUNT = 10;
public static String generateHtmlReport(SQLiteDatabase db, int days) { public static String generateHtmlReport(SQLiteDatabase db, int days) {
if (db == null) { if (db == null) {
return "<html><body><h1>Error: Database not available</h1></body></html>"; return "<html><body><h1>Error: Database not available</h1></body></html>";
} }
// Fetch full calendar months for the report selector. This avoids the month dropdown // Read locally stored alarm events. The report's month selector is built
// losing the previous month when the app moves into a new month. // from the timestamps actually present in this result, so older months do not vanish
// just because the current calendar month changes.
String query = "SELECT dataTime, status, type, notes, dataJSON " + String query = "SELECT dataTime, status, type, notes, dataJSON " +
"FROM events " + "FROM events " +
"WHERE dataTime >= date('now', 'start of month', '-" + (MONTHS_TO_SHOW - 1) + " months') " + "WHERE status IN (2, 5) " +
"AND status IN (1, 2, 3, 5) " +
"ORDER BY dataTime ASC"; "ORDER BY dataTime ASC";
Cursor cursor = null; Cursor cursor = null;
@@ -47,11 +53,19 @@ public class ReportManager {
event.type = cursor.getString(2); event.type = cursor.getString(2);
event.notes = cursor.getString(3); event.notes = cursor.getString(3);
event.durationSeconds = parseDurationSecondsFromNotes(event.notes);
event.hr = parseHeartRateFromNotes(event.notes);
try { try {
String dataJson = cursor.getString(4); String dataJson = cursor.getString(4);
if (dataJson != null) { if (dataJson != null && dataJson.trim().length() > 0) {
JSONObject jo = new JSONObject(dataJson); JSONObject jo = new JSONObject(dataJson);
event.hr = jo.optDouble("hr", 0.0); double jsonHr = optPositiveDouble(jo, "hr");
if (jsonHr <= 0.0) jsonHr = optPositiveDouble(jo, "HR");
if (jsonHr <= 0.0) jsonHr = optPositiveDouble(jo, "mHR");
if (jsonHr <= 0.0) jsonHr = optPositiveDouble(jo, "heartRate");
if (jsonHr > 0.0) event.hr = jsonHr;
event.alarmPhrase = jo.optString("alarmPhrase", ""); event.alarmPhrase = jo.optString("alarmPhrase", "");
event.alarmCause = jo.optString("alarmCause", "").trim(); event.alarmCause = jo.optString("alarmCause", "").trim();
} }
@@ -61,6 +75,11 @@ public class ReportManager {
try { try {
event.date = sdf.parse(event.dataTime); event.date = sdf.parse(event.dataTime);
if (event.date != null && event.durationSeconds > 0) {
event.inferredStartDate = new Date(event.date.getTime() - event.durationSeconds * 1000L);
} else {
event.inferredStartDate = event.date;
}
} catch (ParseException e) { } catch (ParseException e) {
Log.w(TAG, "Error parsing date: " + event.dataTime); Log.w(TAG, "Error parsing date: " + event.dataTime);
} }
@@ -74,10 +93,101 @@ public class ReportManager {
if (cursor != null) cursor.close(); if (cursor != null) cursor.close();
} }
if (ADD_DEMO_EVENTS) {
addDemoEvents(rawEvents);
}
Collections.sort(rawEvents, (event1, event2) -> {
if (event1.date == null && event2.date == null) return 0;
if (event1.date == null) return 1;
if (event2.date == null) return -1;
return event1.date.compareTo(event2.date);
});
ArrayList<SeizureGroup> groups = groupEvents(rawEvents); ArrayList<SeizureGroup> groups = groupEvents(rawEvents);
return buildCalendarHtml(groups, days); return buildCalendarHtml(groups, days);
} }
private static void addDemoEvents(ArrayList<SeizureEvent> events) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.UK);
Calendar monthStart = Calendar.getInstance();
monthStart.set(Calendar.YEAR, 2026);
monthStart.set(Calendar.MONTH, Calendar.APRIL); // April is month 3 internally
monthStart.set(Calendar.DAY_OF_MONTH, 1);
monthStart.set(Calendar.HOUR_OF_DAY, 0);
monthStart.set(Calendar.MINUTE, 0);
monthStart.set(Calendar.SECOND, 0);
monthStart.set(Calendar.MILLISECOND, 0);
Calendar monthEnd = (Calendar) monthStart.clone();
monthEnd.add(Calendar.MONTH, 1);
monthEnd.add(Calendar.SECOND, -1);
int daysInMonth = monthStart.getActualMaximum(Calendar.DAY_OF_MONTH);
// 7 evening events, 2 night events, 1 afternoon event.
// Night: 00-06
// Afternoon: 12-18
// Evening: 18-24
int[] demoDays = {
1, 4, 4, 7, 12,
15, 19, 22, 28, 29
};
// 7 evening events, 2 night events, 1 afternoon event.
// Day 4 has two events.
int[] demoHours = {
23, 2, 21, 19, 4,
22, 20, 15, 18, 23
};
int[] demoMinutes = {
41, 16, 53, 9, 34,
27, 48, 22, 5, 37
};
int[] demoDurationsSecs = {
95, 125, 65, 140, 85,
110, 180, 100, 55, 75
};
int[] demoHr = {
99, 107, 88, 112, 86,
104, 121, 100, 91, 95
};
int count = Math.min(DEMO_EVENT_COUNT, demoDays.length);
for (int i = 0; i < count; i++) {
int day = demoDays[i];
if (day > daysInMonth) {
continue;
}
Calendar eventCal = (Calendar) monthStart.clone();
eventCal.set(Calendar.DAY_OF_MONTH, day);
eventCal.set(Calendar.HOUR_OF_DAY, demoHours[i]);
eventCal.set(Calendar.MINUTE, demoMinutes[i]);
eventCal.set(Calendar.SECOND, 0);
eventCal.set(Calendar.MILLISECOND, 0);
SeizureEvent event = new SeizureEvent();
event.date = eventCal.getTime();
event.dataTime = sdf.format(event.date);
event.status = 2; // ALARM only
event.type = "seizure";
event.durationSeconds = demoDurationsSecs[i];
event.inferredStartDate = new Date(event.date.getTime() - event.durationSeconds * 1000L);
event.notes = "Duration: " + demoDurationsSecs[i] + "s HR: " + demoHr[i];
event.hr = demoHr[i];
event.alarmPhrase = "Demo alarm";
event.alarmCause = "Demo data";
events.add(event);
}
}
private static ArrayList<SeizureGroup> groupEvents(ArrayList<SeizureEvent> events) { private static ArrayList<SeizureGroup> groupEvents(ArrayList<SeizureEvent> events) {
ArrayList<SeizureGroup> groups = new ArrayList<>(); ArrayList<SeizureGroup> groups = new ArrayList<>();
if (events.isEmpty()) return groups; if (events.isEmpty()) return groups;
@@ -87,27 +197,13 @@ public class ReportManager {
for (SeizureEvent event : events) { for (SeizureEvent event : events) {
if (currentGroup == null) { if (currentGroup == null) {
currentGroup = new SeizureGroup(event); currentGroup = new SeizureGroup(event);
} else { } else if (shouldMergeIntoGroup(currentGroup, event)) {
long diffSecs = 0; currentGroup.addEvent(event);
if (event.date != null && currentGroup.lastDate != null) {
diffSecs = (event.date.getTime() - currentGroup.lastDate.getTime()) / 1000;
}
if (diffSecs <= GROUP_THRESHOLD_SECS) {
currentGroup.lastDate = event.date;
currentGroup.lastEvent = event;
if (event.notes != null && event.notes.contains("Duration:")) {
currentGroup.durationStr = extractDuration(event.notes);
}
if (event.hr > 0) {
currentGroup.hr = event.hr;
}
} else { } else {
groups.add(currentGroup); groups.add(currentGroup);
currentGroup = new SeizureGroup(event); currentGroup = new SeizureGroup(event);
} }
} }
}
if (currentGroup != null) { if (currentGroup != null) {
groups.add(currentGroup); groups.add(currentGroup);
@@ -116,10 +212,172 @@ public class ReportManager {
return groups; return groups;
} }
private static boolean shouldMergeIntoGroup(SeizureGroup currentGroup, SeizureEvent event) {
if (currentGroup == null || event == null) return false;
// Preferred rule: rows from the same continuing seizure usually have a similar
// inferred start time because alarmDuration grows as the same alarm continues.
// This avoids chaining two distinct seizures together just because their log rows
// are near each other in time.
if (currentGroup.inferredStartDate != null && event.inferredStartDate != null
&& currentGroup.hasDurationEvidence && event.durationSeconds > 0) {
long startDiffSecs = Math.abs(event.inferredStartDate.getTime()
- currentGroup.inferredStartDate.getTime()) / 1000L;
return startDiffSecs <= SAME_SEIZURE_START_TOLERANCE_SECS;
}
// Fallback for old/manual rows with no duration in notes. Keep this deliberately
// short so nearby but separate seizures are less likely to be merged.
if (event.date != null && currentGroup.lastDate != null) {
long diffSecs = (event.date.getTime() - currentGroup.lastDate.getTime()) / 1000L;
return diffSecs >= 0 && diffSecs <= FALLBACK_GROUP_THRESHOLD_SECS;
}
return false;
}
private static double optPositiveDouble(JSONObject jo, String key) {
try {
if (jo == null || !jo.has(key) || jo.isNull(key)) return 0.0;
double value = jo.optDouble(key, 0.0);
return value > 0.0 ? value : 0.0;
} catch (Exception ignored) {
return 0.0;
}
}
private static int parseDurationSecondsFromNotes(String notes) {
String duration = extractDuration(notes);
if (duration == null || duration.equals("unknown") || duration.equals("N/A")) return 0;
return parseDurationSeconds(duration);
}
private static double parseHeartRateFromNotes(String notes) {
if (notes == null) return 0.0;
String lower = notes.toLowerCase(Locale.UK);
int idx = lower.indexOf("hr:");
if (idx < 0) idx = lower.indexOf("heart rate:");
if (idx < 0) return 0.0;
int start = notes.indexOf(':', idx);
if (start < 0 || start + 1 >= notes.length()) return 0.0;
StringBuilder number = new StringBuilder();
for (int i = start + 1; i < notes.length(); i++) {
char ch = notes.charAt(i);
if ((ch >= '0' && ch <= '9') || ch == '.') {
number.append(ch);
} else if (number.length() > 0) {
break;
}
}
try {
double value = Double.parseDouble(number.toString());
return value > 0.0 ? value : 0.0;
} catch (Exception ignored) {
return 0.0;
}
}
private static int parseDurationSeconds(String durationStr) {
if (durationStr == null) return 0;
String s = durationStr.trim().toLowerCase(Locale.UK);
if (s.isEmpty() || s.equals("n/a") || s.equals("unknown")) {
return 0;
}
try {
// Supports HH:MM:SS
if (s.matches("\\d{1,2}:\\d{1,2}:\\d{1,2}")) {
String[] parts = s.split(":");
int hours = Integer.parseInt(parts[0]);
int mins = Integer.parseInt(parts[1]);
int secs = Integer.parseInt(parts[2]);
return Math.max(1, hours * 3600 + mins * 60 + secs);
}
// Supports MM:SS
if (s.matches("\\d{1,2}:\\d{1,2}")) {
String[] parts = s.split(":");
int mins = Integer.parseInt(parts[0]);
int secs = Integer.parseInt(parts[1]);
return Math.max(1, mins * 60 + secs);
}
// Supports compact values like "123s", "123 sec", "2 min 30 sec".
int totalSeconds = 0;
String[] tokens = s.replace(",", " ")
.replace("seconds", " sec")
.replace("second", " sec")
.replace("secs", " sec")
.replace("minutes", " min")
.replace("minute", " min")
.replace("mins", " min")
.replace("hours", " hour")
.replace("hrs", " hour")
.replace("hr", " hour")
.split("\\s+");
for (int i = 0; i < tokens.length; i++) {
String token = tokens[i];
int value = -1;
try {
if (token.endsWith("s") && token.length() > 1) {
value = Integer.parseInt(token.substring(0, token.length() - 1));
totalSeconds += value;
continue;
}
value = Integer.parseInt(token);
} catch (NumberFormatException ignored) {
continue;
}
String unit = (i + 1 < tokens.length) ? tokens[i + 1] : "sec";
if (unit.startsWith("hour")) {
totalSeconds += value * 3600;
} else if (unit.startsWith("min")) {
totalSeconds += value * 60;
} else if (unit.startsWith("sec") || unit.equals("s")) {
totalSeconds += value;
} else {
// Bare number in the existing local notes is normally seconds.
totalSeconds += value;
}
}
if (totalSeconds > 0) return totalSeconds;
} catch (Exception ignored) {
}
return 0;
}
private static String formatDurationSeconds(int durationSeconds) {
if (durationSeconds <= 0) return "N/A";
if (durationSeconds < 60) return durationSeconds + "s";
int hours = durationSeconds / 3600;
int mins = (durationSeconds % 3600) / 60;
int secs = durationSeconds % 60;
if (hours > 0) {
return String.format(Locale.UK, "%dh %02dm %02ds", hours, mins, secs);
}
if (secs > 0) {
return String.format(Locale.UK, "%dm %02ds", mins, secs);
}
return mins + "m";
}
private static String extractDuration(String notes) { private static String extractDuration(String notes) {
try { try {
int start = notes.indexOf("Duration:") + 9; if (notes == null) return "unknown";
int end = notes.indexOf("HR:"); int durationIdx = notes.indexOf("Duration:");
if (durationIdx < 0) return "unknown";
int start = durationIdx + 9;
int end = notes.indexOf("HR:", start);
if (end == -1) end = notes.length(); if (end == -1) end = notes.length();
return notes.substring(start, end).trim(); return notes.substring(start, end).trim();
} catch (Exception e) { } catch (Exception e) {
@@ -157,57 +415,9 @@ public class ReportManager {
} }
private static int parseDurationMinutes(String durationStr) { private static int parseDurationMinutes(String durationStr) {
if (durationStr == null) return 5; int seconds = parseDurationSeconds(durationStr);
if (seconds <= 0) return DEFAULT_MARKER_DURATION_SECS / 60;
String s = durationStr.trim().toLowerCase(Locale.UK); return Math.max(1, (int) Math.ceil(seconds / 60.0));
if (s.isEmpty() || s.equals("n/a") || s.equals("unknown")) {
return 5;
}
try {
// Supports HH:MM:SS
if (s.matches("\\d{1,2}:\\d{1,2}:\\d{1,2}")) {
String[] parts = s.split(":");
int hours = Integer.parseInt(parts[0]);
int mins = Integer.parseInt(parts[1]);
int secs = Integer.parseInt(parts[2]);
int totalSeconds = hours * 3600 + mins * 60 + secs;
return Math.max(1, (int) Math.ceil(totalSeconds / 60.0));
}
// Supports MM:SS
if (s.matches("\\d{1,2}:\\d{1,2}")) {
String[] parts = s.split(":");
int mins = Integer.parseInt(parts[0]);
int secs = Integer.parseInt(parts[1]);
int totalSeconds = mins * 60 + secs;
return Math.max(1, (int) Math.ceil(totalSeconds / 60.0));
}
// Supports text like "2 min 30 sec" or "1 hour 5 min"
int totalMinutes = 0;
String[] tokens = s.replace(",", " ").split("\\s+");
for (int i = 0; i < tokens.length - 1; i++) {
try {
int value = Integer.parseInt(tokens[i]);
String unit = tokens[i + 1];
if (unit.startsWith("hour") || unit.startsWith("hr")) {
totalMinutes += value * 60;
} else if (unit.startsWith("min")) {
totalMinutes += value;
} else if (unit.startsWith("sec")) {
if (value > 0) totalMinutes += 1;
}
} catch (NumberFormatException ignored) {
// Keep checking the rest of the duration text.
}
}
if (totalMinutes > 0) return totalMinutes;
} catch (Exception ignored) {
}
return 5;
} }
private static String getStatusColor(int status) { private static String getStatusColor(int status) {
@@ -271,7 +481,7 @@ public class ReportManager {
SimpleDateFormat monthFormat = new SimpleDateFormat("MMMM yyyy", Locale.UK); SimpleDateFormat monthFormat = new SimpleDateFormat("MMMM yyyy", Locale.UK);
SimpleDateFormat monthIdFormat = new SimpleDateFormat("yyyy-MM", Locale.UK); SimpleDateFormat monthIdFormat = new SimpleDateFormat("yyyy-MM", Locale.UK);
// Group seizures by day and by month. // Group alarms by day and by month.
Map<String, ArrayList<SeizureGroup>> byDay = new HashMap<>(); Map<String, ArrayList<SeizureGroup>> byDay = new HashMap<>();
Map<String, Integer> eventsByMonth = new HashMap<>(); Map<String, Integer> eventsByMonth = new HashMap<>();
for (SeizureGroup g : groups) { for (SeizureGroup g : groups) {
@@ -291,37 +501,40 @@ public class ReportManager {
Calendar todayCal = Calendar.getInstance(); Calendar todayCal = Calendar.getInstance();
Date today = todayCal.getTime(); Date today = todayCal.getTime();
// Build a fixed list of recent full months so the dropdown remains useful across // Build the selector from months that actually have event data. Newest months are
// month boundaries, for example allowing April to be selected after May begins. // shown first. The current month is selected only if it has events; otherwise the
Calendar firstMonth = Calendar.getInstance(); // latest available month is selected.
firstMonth.add(Calendar.MONTH, -(MONTHS_TO_SHOW - 1)); ArrayList<String> monthIds = new ArrayList<>(eventsByMonth.keySet());
firstMonth.set(Calendar.DAY_OF_MONTH, 1); Collections.sort(monthIds);
firstMonth.set(Calendar.HOUR_OF_DAY, 0); Collections.reverse(monthIds);
firstMonth.set(Calendar.MINUTE, 0);
firstMonth.set(Calendar.SECOND, 0);
firstMonth.set(Calendar.MILLISECOND, 0);
Calendar lastMonth = Calendar.getInstance(); String currentMonthId = monthIdFormat.format(today);
lastMonth.set(Calendar.DAY_OF_MONTH, 1); String defaultMonthId = monthIds.contains(currentMonthId)
lastMonth.set(Calendar.HOUR_OF_DAY, 0); ? currentMonthId
lastMonth.set(Calendar.MINUTE, 0); : (monthIds.isEmpty() ? currentMonthId : monthIds.get(0));
lastMonth.set(Calendar.SECOND, 0);
lastMonth.set(Calendar.MILLISECOND, 0);
ArrayList<Calendar> reportMonths = new ArrayList<>(); ArrayList<Calendar> reportMonths = new ArrayList<>();
Calendar monthCursor = (Calendar) firstMonth.clone(); for (String monthId : monthIds) {
while (!monthCursor.after(lastMonth)) { try {
reportMonths.add((Calendar) monthCursor.clone()); Date monthDate = monthIdFormat.parse(monthId);
monthCursor.add(Calendar.MONTH, 1); Calendar cal = Calendar.getInstance();
cal.setTime(monthDate);
cal.set(Calendar.DAY_OF_MONTH, 1);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
reportMonths.add(cal);
} catch (ParseException e) {
Log.w(TAG, "Error parsing report month: " + monthId);
}
} }
String defaultMonthId = monthIdFormat.format(today);
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("<!DOCTYPE html><html><head>"); sb.append("<!DOCTYPE html><html><head>");
sb.append("<meta charset='UTF-8'>"); sb.append("<meta charset='UTF-8'>");
sb.append("<meta name='viewport' content='width=device-width, initial-scale=1'>"); sb.append("<meta name='viewport' content='width=device-width, initial-scale=1'>");
sb.append("<title>ClinX02 Seizure Report</title>"); sb.append("<title>FLOGA Seizure Report</title>");
sb.append("<style>"); sb.append("<style>");
sb.append("* { box-sizing: border-box; }"); sb.append("* { box-sizing: border-box; }");
sb.append("body { font-family: Arial, sans-serif; margin: 0; padding: 16px; background: #eef1f5; color: #222; }"); sb.append("body { font-family: Arial, sans-serif; margin: 0; padding: 16px; background: #eef1f5; color: #222; }");
@@ -367,7 +580,7 @@ public class ReportManager {
sb.append(".day-header { text-align: center; font-weight: bold; padding: 6px; color: #555; font-size: 13px; }"); sb.append(".day-header { text-align: center; font-weight: bold; padding: 6px; color: #555; font-size: 13px; }");
sb.append(".day-box { background: white; border-radius: 8px; padding: 6px; min-height: 86px; border: 1px solid #ddd; box-shadow: 0 1px 4px rgba(0,0,0,0.04); }"); sb.append(".day-box { background: white; border-radius: 8px; padding: 6px; min-height: 86px; border: 1px solid #ddd; box-shadow: 0 1px 4px rgba(0,0,0,0.04); }");
sb.append(".day-box.today { border: 2px solid #2563eb; }"); sb.append(".day-box.today { border: 2px solid #2563eb; }");
sb.append(".day-box.has-seizures { background: #fff8f8; border-color: #fecaca; }"); sb.append(".day-box.has-alarms { background: #fff8f8; border-color: #fecaca; }");
sb.append(".day-box.empty { background: transparent; border: none; box-shadow: none; }"); sb.append(".day-box.empty { background: transparent; border: none; box-shadow: none; }");
sb.append(".day-num { font-size: 13px; font-weight: bold; color: #333; margin-bottom: 4px; }"); sb.append(".day-num { font-size: 13px; font-weight: bold; color: #333; margin-bottom: 4px; }");
sb.append(".timeline { position: relative; height: 28px; margin-top: 6px; border-radius: 6px; overflow: hidden; border: 1px solid #d1d5db; background: white; isolation: isolate; }"); sb.append(".timeline { position: relative; height: 28px; margin-top: 6px; border-radius: 6px; overflow: hidden; border: 1px solid #d1d5db; background: white; isolation: isolate; }");
@@ -381,9 +594,9 @@ public class ReportManager {
sb.append(".time-marker { position: absolute; top: 0; bottom: 0; width: 1px; background: rgba(0,0,0,0.16); }"); sb.append(".time-marker { position: absolute; top: 0; bottom: 0; width: 1px; background: rgba(0,0,0,0.16); }");
sb.append(".event-bar { position: absolute; top: 5px; height: 18px; border-radius: 4px; opacity: 0.96; border: 1px solid rgba(0,0,0,0.18); z-index: 2; }"); sb.append(".event-bar { position: absolute; top: 5px; height: 18px; border-radius: 4px; opacity: 0.96; border: 1px solid rgba(0,0,0,0.18); z-index: 2; }");
sb.append(".time-scale { display: flex; justify-content: space-between; font-size: 9px; color: #6b7280; margin-top: 3px; }"); sb.append(".time-scale { display: flex; justify-content: space-between; font-size: 9px; color: #6b7280; margin-top: 3px; }");
sb.append(".seizure-count { font-size: 11px; color: #6b7280; margin-top: 4px; }"); sb.append(".alarm-count { font-size: 11px; color: #6b7280; margin-top: 4px; }");
sb.append(".table-wrap { width: 100%; overflow-x: auto; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); }"); sb.append(".table-wrap { width: 100%; overflow-x: auto; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); }");
sb.append(".detail-table { width: 100%; border-collapse: collapse; background: white; overflow: hidden; min-width: 680px; }"); sb.append(".detail-table { width: 100%; border-collapse: collapse; background: white; overflow: hidden; min-width: 600px; }");
sb.append(".detail-table th { background: #2563eb; color: white; padding: 12px; text-align: left; font-size: 13px; }"); sb.append(".detail-table th { background: #2563eb; color: white; padding: 12px; text-align: left; font-size: 13px; }");
sb.append(".detail-table td { padding: 10px 12px; border-bottom: 1px solid #eee; font-size: 13px; vertical-align: top; }"); sb.append(".detail-table td { padding: 10px 12px; border-bottom: 1px solid #eee; font-size: 13px; vertical-align: top; }");
sb.append(".detail-table tr:hover { background: #f9fafb; }"); sb.append(".detail-table tr:hover { background: #f9fafb; }");
@@ -410,31 +623,31 @@ public class ReportManager {
sb.append(".timeline { height: 22px; }"); sb.append(".timeline { height: 22px; }");
sb.append(".event-bar { top: 4px; height: 14px; }"); sb.append(".event-bar { top: 4px; height: 14px; }");
sb.append(".time-scale { font-size: 8px; }"); sb.append(".time-scale { font-size: 8px; }");
sb.append(".seizure-count { font-size: 10px; }"); sb.append(".alarm-count { font-size: 10px; }");
sb.append(".detail-table th, .detail-table td { font-size: 11px; padding: 6px; }"); sb.append(".detail-table th, .detail-table td { font-size: 11px; padding: 6px; }");
sb.append("}"); sb.append("}");
sb.append("</style></head><body>"); sb.append("</style></head><body>");
sb.append("<div class='container'>"); sb.append("<div class='container'>");
sb.append("<h1>ClinX02 Seizure Report</h1>"); sb.append("<h1>FLOGA Seizure Report</h1>");
sb.append("<p class='subtitle'>Calendar timeline overview and detailed event log</p>"); sb.append("<p class='subtitle'>Calendar timeline overview and detailed alarm log</p>");
// Summary box // Summary box
sb.append("<div class='summary'>"); sb.append("<div class='summary'>");
sb.append("<div class='stats'>"); sb.append("<div class='stats'>");
sb.append("<div class='stat-card'>"); sb.append("<div class='stat-card'>");
sb.append("<div class='stat-label'>Calendar range</div>"); sb.append("<div class='stat-label'>Months with events</div>");
sb.append("<div class='stat-value'>").append(MONTHS_TO_SHOW).append(" months</div>"); sb.append("<div class='stat-value'>").append(eventsByMonth.size()).append("</div>");
sb.append("</div>"); sb.append("</div>");
sb.append("<div class='stat-card'>"); sb.append("<div class='stat-card'>");
sb.append("<div class='stat-label'>Total seizure events</div>"); sb.append("<div class='stat-label'>Total alarm events</div>");
sb.append("<div class='stat-value'>").append(groups.size()).append("</div>"); sb.append("<div class='stat-value'>").append(groups.size()).append("</div>");
sb.append("</div>"); sb.append("</div>");
sb.append("<div class='stat-card'>"); sb.append("<div class='stat-card'>");
sb.append("<div class='stat-label'>Days with seizures</div>"); sb.append("<div class='stat-label'>Days with alarms</div>");
sb.append("<div class='stat-value'>").append(byDay.size()).append("</div>"); sb.append("<div class='stat-value'>").append(byDay.size()).append("</div>");
sb.append("</div>"); sb.append("</div>");
@@ -443,18 +656,6 @@ public class ReportManager {
.append("&token=clinx02secure' download='seizure_report.html'>Download Report</a>"); .append("&token=clinx02secure' download='seizure_report.html'>Download Report</a>");
sb.append("</div>"); sb.append("</div>");
// Legend
sb.append("<div class='legend'>");
sb.append("<div class='legend-item'><div class='legend-dot' style='background:#e5e7eb'></div>Night background (00-06)</div>");
sb.append("<div class='legend-item'><div class='legend-dot' style='background:#fde68a'></div>Morning background (06-12)</div>");
sb.append("<div class='legend-item'><div class='legend-dot' style='background:#fdba74'></div>Afternoon background (12-18)</div>");
sb.append("<div class='legend-item'><div class='legend-dot' style='background:#c4b5fd'></div>Evening background (18-24)</div>");
sb.append("<div class='legend-item'><div class='legend-dot' style='background:#f59e0b'></div>Warning bar</div>");
sb.append("<div class='legend-item'><div class='legend-dot' style='background:#ef4444'></div>Alarm bar</div>");
sb.append("<div class='legend-item'><div class='legend-dot' style='background:#8b5cf6'></div>Fall bar</div>");
sb.append("<div class='legend-item'><div class='legend-dot' style='background:#06b6d4'></div>Manual bar</div>");
sb.append("</div>");
// Month selector // Month selector
sb.append("<div class='month-controls'>"); sb.append("<div class='month-controls'>");
sb.append("<label for='monthSelect'>Month:</label>"); sb.append("<label for='monthSelect'>Month:</label>");
@@ -466,7 +667,7 @@ public class ReportManager {
int count = eventCount == null ? 0 : eventCount; int count = eventCount == null ? 0 : eventCount;
sb.append("<option value='").append(monthId).append("'").append(selected).append(">"); sb.append("<option value='").append(monthId).append("'").append(selected).append(">");
sb.append(monthFormat.format(reportMonth.getTime())).append(" (").append(count) sb.append(monthFormat.format(reportMonth.getTime())).append(" (").append(count)
.append(count == 1 ? " event" : " events").append(")"); .append(count == 1 ? " alarm" : " alarms").append(")");
sb.append("</option>"); sb.append("</option>");
} }
sb.append("</select>"); sb.append("</select>");
@@ -505,7 +706,7 @@ public class ReportManager {
sb.append("<div class='month-summary-panel"); sb.append("<div class='month-summary-panel");
if (activeMonth) sb.append(" active"); if (activeMonth) sb.append(" active");
sb.append("' id='summary-").append(monthId).append("' data-month='").append(monthId).append("'>"); sb.append("' id='summary-").append(monthId).append("' data-month='").append(monthId).append("'>");
sb.append("<div class='summary-chart-title'>Seizures by time of day</div>"); sb.append("<div class='summary-chart-title'>Alarms by time of day</div>");
appendSummaryBar(sb, "Night", "00-06", "night", nightCount, maxTimeCount); appendSummaryBar(sb, "Night", "00-06", "night", nightCount, maxTimeCount);
appendSummaryBar(sb, "Morning", "06-12", "morning", morningCount, maxTimeCount); appendSummaryBar(sb, "Morning", "06-12", "morning", morningCount, maxTimeCount);
appendSummaryBar(sb, "Afternoon", "12-18", "afternoon", afternoonCount, maxTimeCount); appendSummaryBar(sb, "Afternoon", "12-18", "afternoon", afternoonCount, maxTimeCount);
@@ -513,6 +714,16 @@ public class ReportManager {
sb.append("</div>"); sb.append("</div>");
} }
// Calendar legend
sb.append("<div class='legend'>");
sb.append("<div class='legend-item'><div class='legend-dot' style='background:#e5e7eb'></div>Night background (00-06)</div>");
sb.append("<div class='legend-item'><div class='legend-dot' style='background:#fde68a'></div>Morning background (06-12)</div>");
sb.append("<div class='legend-item'><div class='legend-dot' style='background:#fdba74'></div>Afternoon background (12-18)</div>");
sb.append("<div class='legend-item'><div class='legend-dot' style='background:#c4b5fd'></div>Evening background (18-24)</div>");
sb.append("<div class='legend-item'><div class='legend-dot' style='background:#ef4444'></div>Alarm bar</div>");
sb.append("<div class='legend-item'><div class='legend-dot' style='background:#06b6d4'></div>Manual alarm bar</div>");
sb.append("</div>");
// Calendar months // Calendar months
for (Calendar reportMonth : reportMonths) { for (Calendar reportMonth : reportMonths) {
String monthId = monthIdFormat.format(reportMonth.getTime()); String monthId = monthIdFormat.format(reportMonth.getTime());
@@ -549,7 +760,7 @@ public class ReportManager {
String boxClass = "day-box"; String boxClass = "day-box";
if (isToday) boxClass += " today"; if (isToday) boxClass += " today";
if (dayGroups != null && !dayGroups.isEmpty()) boxClass += " has-seizures"; if (dayGroups != null && !dayGroups.isEmpty()) boxClass += " has-alarms";
sb.append("<div class='").append(boxClass).append("'>"); sb.append("<div class='").append(boxClass).append("'>");
sb.append("<div class='day-num'>").append(day).append("</div>"); sb.append("<div class='day-num'>").append(day).append("</div>");
@@ -570,9 +781,12 @@ public class ReportManager {
for (SeizureGroup g : dayGroups) { for (SeizureGroup g : dayGroups) {
int startMinutes = getMinutesOfDay(g.startDate); int startMinutes = getMinutesOfDay(g.startDate);
int durationMinutes = parseDurationMinutes(g.durationStr); int timelineSeconds = g.durationSeconds > 0
? Math.min(g.durationSeconds, MAX_TIMELINE_DURATION_SECS)
: DEFAULT_MARKER_DURATION_SECS;
int durationMinutes = Math.max(1, (int) Math.ceil(timelineSeconds / 60.0));
double leftPct = (startMinutes / 1440.0) * 100.0; double leftPct = Math.max(0.0, Math.min(100.0, (startMinutes / 1440.0) * 100.0));
double widthPct = Math.max((durationMinutes / 1440.0) * 100.0, 1.4); double widthPct = Math.max((durationMinutes / 1440.0) * 100.0, 1.4);
if (leftPct + widthPct > 100.0) { if (leftPct + widthPct > 100.0) {
@@ -597,8 +811,8 @@ public class ReportManager {
sb.append("</div>"); sb.append("</div>");
sb.append("<div class='time-scale'><span>00</span><span>06</span><span>12</span><span>18</span><span>24</span></div>"); sb.append("<div class='time-scale'><span>00</span><span>06</span><span>12</span><span>18</span><span>24</span></div>");
sb.append("<div class='seizure-count'>").append(dayGroups.size()) sb.append("<div class='alarm-count'>").append(dayGroups.size())
.append(dayGroups.size() == 1 ? " event" : " events").append("</div>"); .append(dayGroups.size() == 1 ? " alarm" : " alarms").append("</div>");
} }
sb.append("</div>"); sb.append("</div>");
@@ -609,19 +823,18 @@ public class ReportManager {
} }
// Detailed table below. JavaScript filters this table to the selected month. // Detailed table below. JavaScript filters this table to the selected month.
sb.append("<h2>Event Details</h2>"); sb.append("<h2>Alarm Details</h2>");
if (groups.isEmpty()) { if (groups.isEmpty()) {
sb.append("<p>No seizure events recorded in this period.</p>"); sb.append("<p>No alarm events recorded in this period.</p>");
} else { } else {
sb.append("<p id='emptyMonthMessage' class='no-month-events'>No seizure events recorded for the selected month.</p>"); sb.append("<p id='emptyMonthMessage' class='no-month-events'>No alarm events recorded for the selected month.</p>");
sb.append("<div class='table-controls' id='eventTableControls'>"); sb.append("<div class='table-controls' id='eventTableControls'>");
sb.append("<div><label for='rowsPerPage'>Entries per page: </label>"); sb.append("<div><label for='rowsPerPage'>Entries per page: </label>");
sb.append("<select id='rowsPerPage' onchange='changePageSize()'>"); sb.append("<select id='rowsPerPage' onchange='changePageSize()'>");
sb.append("<option value='5'>5</option>");
sb.append("<option value='10' selected>10</option>"); sb.append("<option value='10' selected>10</option>");
sb.append("<option value='25'>25</option>"); sb.append("<option value='25'>25</option>");
sb.append("<option value='50'>50</option>"); sb.append("<option value='50'>50</option>");
sb.append("<option value='all'>All</option>"); sb.append("<option value='100'>100</option>");
sb.append("</select></div>"); sb.append("</select></div>");
sb.append("<div class='pager'>"); sb.append("<div class='pager'>");
sb.append("<button type='button' id='prevPageBtn' onclick='previousPage()'>Previous</button>"); sb.append("<button type='button' id='prevPageBtn' onclick='previousPage()'>Previous</button>");
@@ -632,16 +845,15 @@ public class ReportManager {
sb.append("<div id='tableInfo' class='table-info'></div>"); sb.append("<div id='tableInfo' class='table-info'></div>");
sb.append("</div>"); sb.append("</div>");
sb.append("<div class='table-wrap' id='eventTableWrap'><table class='detail-table'>"); sb.append("<div class='table-wrap' id='eventTableWrap'><table class='detail-table'>");
sb.append("<tr><th>#</th><th>Date &amp; Time</th><th>Status</th><th>Duration</th><th>Heart Rate</th><th>Cause</th></tr>"); sb.append("<tr><th>#</th><th>Date &amp; Time</th><th>Status</th><th>Duration</th><th>Heart Rate</th></tr>");
SimpleDateFormat displaySdf = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss", Locale.UK); SimpleDateFormat displaySdf = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss", Locale.UK);
int i = 1; int i = 1;
for (SeizureGroup g : groups) { for (int gi = groups.size() - 1; gi >= 0; gi--) {
SeizureGroup g = groups.get(gi);
String timeStr = g.startDate != null ? displaySdf.format(g.startDate) : g.firstEvent.dataTime; String timeStr = g.startDate != null ? displaySdf.format(g.startDate) : g.firstEvent.dataTime;
String statusStr = statusToString(g.firstEvent.status); String statusStr = statusToString(g.firstEvent.status);
String cssClass = statusToCssClass(g.firstEvent.status); String cssClass = statusToCssClass(g.firstEvent.status);
String hrStr = g.hr > 0 ? String.format(Locale.UK, "%.1f bpm", g.hr) : "N/A"; String hrStr = g.hr > 0 ? String.format(Locale.UK, "%d bpm", Math.round(g.hr)) : "N/A";
String cause = g.firstEvent.alarmCause != null && !g.firstEvent.alarmCause.isEmpty()
? g.firstEvent.alarmCause : "Unknown";
String dotColor = getTimeOfDayColor(g.startDate); String dotColor = getTimeOfDayColor(g.startDate);
String eventMonth = g.startDate != null ? monthIdFormat.format(g.startDate) : ""; String eventMonth = g.startDate != null ? monthIdFormat.format(g.startDate) : "";
@@ -652,13 +864,12 @@ public class ReportManager {
sb.append("<td><span class='status-pill ").append(cssClass).append("'>").append(statusStr).append("</span></td>"); sb.append("<td><span class='status-pill ").append(cssClass).append("'>").append(statusStr).append("</span></td>");
sb.append("<td>").append(escapeHtml(g.durationStr)).append("</td>"); sb.append("<td>").append(escapeHtml(g.durationStr)).append("</td>");
sb.append("<td>").append(hrStr).append("</td>"); sb.append("<td>").append(hrStr).append("</td>");
sb.append("<td>").append(escapeHtml(cause)).append("</td>");
sb.append("</tr>"); sb.append("</tr>");
} }
sb.append("</table></div>"); sb.append("</table></div>");
} }
sb.append("<p class='footer'><small>Generated by ClinX02 OpenSeizureDetector</small></p>"); sb.append("<p class='footer'><small>Generated by FLOGA</small></p>");
sb.append("</div>"); sb.append("</div>");
sb.append("<script>"); sb.append("<script>");
@@ -754,7 +965,9 @@ public class ReportManager {
String alarmPhrase; String alarmPhrase;
String alarmCause; String alarmCause;
double hr = 0.0; double hr = 0.0;
int durationSeconds = 0;
Date date; Date date;
Date inferredStartDate;
} }
static class SeizureGroup { static class SeizureGroup {
@@ -762,18 +975,53 @@ public class ReportManager {
SeizureEvent lastEvent; SeizureEvent lastEvent;
Date startDate; Date startDate;
Date lastDate; Date lastDate;
Date inferredStartDate;
String durationStr = "N/A"; String durationStr = "N/A";
int durationSeconds = 0;
double hr = 0.0; double hr = 0.0;
boolean hasDurationEvidence = false;
SeizureGroup(SeizureEvent first) { SeizureGroup(SeizureEvent first) {
this.firstEvent = first; this.firstEvent = first;
this.lastEvent = first; this.lastEvent = first;
this.startDate = first.date; this.startDate = first.inferredStartDate != null ? first.inferredStartDate : first.date;
this.lastDate = first.date; this.lastDate = first.date;
if (first.notes != null && first.notes.contains("Duration:")) { this.inferredStartDate = first.inferredStartDate;
this.durationStr = extractDuration(first.notes); this.durationSeconds = first.durationSeconds;
this.hasDurationEvidence = first.durationSeconds > 0;
this.durationStr = first.durationSeconds > 0
? formatDurationSeconds(first.durationSeconds)
: extractDuration(first.notes);
if (this.durationStr == null || this.durationStr.equals("unknown") || this.durationStr.isEmpty()) {
this.durationStr = "N/A";
} }
this.hr = first.hr; this.hr = first.hr;
} }
void addEvent(SeizureEvent event) {
this.lastEvent = event;
this.lastDate = event.date;
if (event.durationSeconds > this.durationSeconds) {
this.durationSeconds = event.durationSeconds;
this.durationStr = formatDurationSeconds(event.durationSeconds);
}
if (event.hr > 0.0) {
this.hr = event.hr;
}
if ((this.firstEvent.alarmCause == null || this.firstEvent.alarmCause.trim().isEmpty())
&& event.alarmCause != null && !event.alarmCause.trim().isEmpty()) {
this.firstEvent.alarmCause = event.alarmCause;
}
if (this.inferredStartDate == null && event.inferredStartDate != null) {
this.inferredStartDate = event.inferredStartDate;
this.startDate = event.inferredStartDate;
}
this.hasDurationEvidence = this.hasDurationEvidence || event.durationSeconds > 0;
}
} }
} }
@@ -73,6 +73,7 @@ import java.text.DecimalFormat;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.List; import java.util.List;
import java.util.Timer; import java.util.Timer;
import java.util.ArrayList;
/** /**
* Based on example at: * Based on example at:
@@ -127,11 +128,14 @@ public class SdServer extends Service implements SdDataReceiver {
private boolean mSMSAlarm = false; private boolean mSMSAlarm = false;
// Emergency-services escalation SMS. // Emergency-services escalation SMS.
// This is separate from the normal carer/contact SMS. // This is separate from the normal carer/contact SMS.
// Emergency-services escalation SMS.
// This is separate from the normal caregiver/contact SMS.
private boolean mEmergencySmsEnabled = false; private boolean mEmergencySmsEnabled = false;
private boolean mEmergencySmsNotifyContacts = true; private boolean mEmergencySmsNotifyContacts = true;
private String mEmergencySmsNumber = ""; private String mEmergencySmsNumber = "";
private String mEmergencySmsMessage = private String mEmergencySmsMessage =
"Possible seizure emergency. The wearer may be having a tonic-clonic seizure and the alarm has remained active."; private int mEmergencySmsDelaySecs = 300; "Possible seizure emergency. The wearer may be having a tonic-clonic seizure and the alarm has remained active.";
private int mEmergencySmsDelaySecs = 300;
private EmergencySmsTimer mEmergencySmsTimer = null; private EmergencySmsTimer mEmergencySmsTimer = null;
private boolean mEmergencySmsSent = false; private boolean mEmergencySmsSent = false;
@@ -441,6 +445,8 @@ public class SdServer extends Service implements SdDataReceiver {
// Stop the Cancel Alarm Latch timer // Stop the Cancel Alarm Latch timer
Log.d(TAG, "onDestroy(): stopping alarm latch timer"); Log.d(TAG, "onDestroy(): stopping alarm latch timer");
stopLatchTimer(); stopLatchTimer();
// Stop the Emergency SMS timer
stopEmergencySmsTimer();
// Stop the location finder. // Stop the location finder.
@@ -636,6 +642,9 @@ public class SdServer extends Service implements SdDataReceiver {
sdData.alarmPhrase = "OK"; sdData.alarmPhrase = "OK";
sdData.alarmStanding = false; sdData.alarmStanding = false;
sdData.fallAlarmStanding = false; sdData.fallAlarmStanding = false;
resetEmergencySmsState();
showNotification(0); showNotification(0);
} }
} }
@@ -644,6 +653,9 @@ public class SdServer extends Service implements SdDataReceiver {
sdData.alarmPhrase = "MUTE"; sdData.alarmPhrase = "MUTE";
sdData.alarmStanding = false; sdData.alarmStanding = false;
sdData.fallAlarmStanding = false; sdData.fallAlarmStanding = false;
resetEmergencySmsState();
showNotification(0); showNotification(0);
} }
// Handle warning alarm state // Handle warning alarm state
@@ -1102,6 +1114,11 @@ public class SdServer extends Service implements SdDataReceiver {
return; return;
} }
if (mEmergencySmsDelaySecs <= 0) {
Log.w(TAG, "startEmergencySmsTimer() - invalid delay, using 300 seconds");
mEmergencySmsDelaySecs = 300;
}
Log.i(TAG, "startEmergencySmsTimer() - starting emergency SMS timer for " Log.i(TAG, "startEmergencySmsTimer() - starting emergency SMS timer for "
+ mEmergencySmsDelaySecs + " seconds"); + mEmergencySmsDelaySecs + " seconds");
@@ -1114,6 +1131,15 @@ public class SdServer extends Service implements SdDataReceiver {
}); });
} }
private void resetEmergencySmsState() {
Log.i(TAG, "resetEmergencySmsState()");
mAlarmStartTime = 0;
mLastAlarmDateStr = "";
mEmergencySmsSent = false;
stopEmergencySmsTimer();
}
private void stopEmergencySmsTimer() { private void stopEmergencySmsTimer() {
if (mEmergencySmsTimer != null) { if (mEmergencySmsTimer != null) {
Log.i(TAG, "stopEmergencySmsTimer() - cancelling emergency SMS timer"); Log.i(TAG, "stopEmergencySmsTimer() - cancelling emergency SMS timer");
@@ -1163,13 +1189,11 @@ public class SdServer extends Service implements SdDataReceiver {
*/ */
public void acceptAlarm() { public void acceptAlarm() {
Log.i(TAG, "acceptAlarm()"); Log.i(TAG, "acceptAlarm()");
mSdData.alarmStanding = false; mSdData.alarmStanding = false;
mSdData.fallAlarmStanding = false; mSdData.fallAlarmStanding = false;
mAlarmStartTime = 0;
mLastAlarmDateStr = "";
mEmergencySmsSent = false;
stopEmergencySmsTimer(); resetEmergencySmsState();
mSdDataSource.acceptAlarm(); mSdDataSource.acceptAlarm();
stopLatchTimer(); stopLatchTimer();
@@ -1618,11 +1642,24 @@ public class SdServer extends Service implements SdDataReceiver {
return; return;
} }
if (msgStr == null) {
Log.w(TAG, "sendSMSDirect() - null message, skipping");
return;
}
Log.i(TAG, "sendSMSDirect() - Sending to " + number); Log.i(TAG, "sendSMSDirect() - Sending to " + number);
Log.i(TAG, "sendSMSDirect() - message length = " + msgStr.length());
try { try {
SmsManager sm = SmsManager.getDefault(); SmsManager sm = SmsManager.getDefault();
if (msgStr.length() > 150) {
java.util.ArrayList<String> parts = sm.divideMessage(msgStr);
Log.i(TAG, "sendSMSDirect() - sending multipart SMS, parts = " + parts.size());
sm.sendMultipartTextMessage(number, null, parts, null, null);
} else {
sm.sendTextMessage(number, null, msgStr, null, null); sm.sendTextMessage(number, null, msgStr, null, null);
}
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "sendSMSDirect - Failed to send SMS Message"); Log.e(TAG, "sendSMSDirect - Failed to send SMS Message");
mUtil.writeToSysLogFile("sendSMSDirect - Failed to send SMS Message"); mUtil.writeToSysLogFile("sendSMSDirect - Failed to send SMS Message");
@@ -1649,20 +1686,21 @@ public class SdServer extends Service implements SdDataReceiver {
String emergencyMessage = mEmergencySmsMessage String emergencyMessage = mEmergencySmsMessage
+ alarmTimePart + alarmTimePart
+ " Alarm duration: " + " Duration: "
+ mEmergencySmsDelaySecs + mEmergencySmsDelaySecs
+ " seconds." + " sec."
+ locationPart + locationPart
+ " Device ID: " + " Device: "
+ shortUuidStr; + shortUuidStr;
Log.i(TAG, "sendEmergencySms() - emergency message length = " + emergencyMessage.length());
Log.i(TAG, "sendEmergencySms() - sending emergency SMS to " + mEmergencySmsNumber); Log.i(TAG, "sendEmergencySms() - sending emergency SMS to " + mEmergencySmsNumber);
Log.i(TAG, "sendEmergencySms() - message: " + emergencyMessage); Log.i(TAG, "sendEmergencySms() - message: " + emergencyMessage);
sendSMSDirect(mEmergencySmsNumber, emergencyMessage); sendSMSDirect(mEmergencySmsNumber.trim(), emergencyMessage);
if (mEmergencySmsNotifyContacts) { if (mEmergencySmsNotifyContacts) {
String contactMessage = "Emergency services have been contacted for seizure alert" String contactMessage = "Emergency services have been contacted for seizure alert."
+ alarmTimePart + alarmTimePart
+ locationPart + locationPart
+ " Device: " + " Device: "
@@ -1780,8 +1818,8 @@ public class SdServer extends Service implements SdDataReceiver {
return; return;
} }
sendEmergencySms();
mEmergencySmsSent = true; mEmergencySmsSent = true;
sendEmergencySms();
} }
@Override @Override
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB