Minor changes to seizure report
This commit is contained in:
@@ -43,7 +43,7 @@
|
||||
android:required="false" />
|
||||
|
||||
<application
|
||||
android:icon="@drawable/star_of_life_48x48"
|
||||
android:icon="@drawable/floga_app_icon"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:theme="@style/AppTheme">
|
||||
@@ -61,18 +61,18 @@
|
||||
<activity android:name=".BLEScanActivity" />
|
||||
<activity android:name=".ExportDataActivity" /> <!-- android:usesCleartextTraffic="true" -->
|
||||
<activity
|
||||
android:name=".StartupActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
android:name=".StartupActivity"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/floga_app_icon">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/star_of_life_48x48"
|
||||
android:icon="@drawable/floga_app_icon"
|
||||
android:label="@string/app_name" />
|
||||
<activity
|
||||
android:name=".PrefActivity"
|
||||
|
||||
@@ -10,27 +10,33 @@ import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class ReportManager {
|
||||
private static final String TAG = "ReportManager";
|
||||
private static final int GROUP_THRESHOLD_SECS = 30;
|
||||
private static final int MONTHS_TO_SHOW = 12;
|
||||
private static final int FALLBACK_GROUP_THRESHOLD_SECS = 30;
|
||||
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) {
|
||||
if (db == null) {
|
||||
return "<html><body><h1>Error: Database not available</h1></body></html>";
|
||||
}
|
||||
|
||||
// Fetch full calendar months for the report selector. This avoids the month dropdown
|
||||
// losing the previous month when the app moves into a new month.
|
||||
// Read locally stored alarm events. The report's month selector is built
|
||||
// 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 " +
|
||||
"FROM events " +
|
||||
"WHERE dataTime >= date('now', 'start of month', '-" + (MONTHS_TO_SHOW - 1) + " months') " +
|
||||
"AND status IN (1, 2, 3, 5) " +
|
||||
"WHERE status IN (2, 5) " +
|
||||
"ORDER BY dataTime ASC";
|
||||
|
||||
Cursor cursor = null;
|
||||
@@ -47,11 +53,19 @@ public class ReportManager {
|
||||
event.type = cursor.getString(2);
|
||||
event.notes = cursor.getString(3);
|
||||
|
||||
event.durationSeconds = parseDurationSecondsFromNotes(event.notes);
|
||||
event.hr = parseHeartRateFromNotes(event.notes);
|
||||
|
||||
try {
|
||||
String dataJson = cursor.getString(4);
|
||||
if (dataJson != null) {
|
||||
if (dataJson != null && dataJson.trim().length() > 0) {
|
||||
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.alarmCause = jo.optString("alarmCause", "").trim();
|
||||
}
|
||||
@@ -61,6 +75,11 @@ public class ReportManager {
|
||||
|
||||
try {
|
||||
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) {
|
||||
Log.w(TAG, "Error parsing date: " + event.dataTime);
|
||||
}
|
||||
@@ -73,11 +92,102 @@ public class ReportManager {
|
||||
} finally {
|
||||
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);
|
||||
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) {
|
||||
ArrayList<SeizureGroup> groups = new ArrayList<>();
|
||||
if (events.isEmpty()) return groups;
|
||||
@@ -87,25 +197,11 @@ public class ReportManager {
|
||||
for (SeizureEvent event : events) {
|
||||
if (currentGroup == null) {
|
||||
currentGroup = new SeizureGroup(event);
|
||||
} else if (shouldMergeIntoGroup(currentGroup, event)) {
|
||||
currentGroup.addEvent(event);
|
||||
} else {
|
||||
long diffSecs = 0;
|
||||
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 {
|
||||
groups.add(currentGroup);
|
||||
currentGroup = new SeizureGroup(event);
|
||||
}
|
||||
groups.add(currentGroup);
|
||||
currentGroup = new SeizureGroup(event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,10 +212,172 @@ public class ReportManager {
|
||||
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) {
|
||||
try {
|
||||
int start = notes.indexOf("Duration:") + 9;
|
||||
int end = notes.indexOf("HR:");
|
||||
if (notes == null) return "unknown";
|
||||
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();
|
||||
return notes.substring(start, end).trim();
|
||||
} catch (Exception e) {
|
||||
@@ -157,57 +415,9 @@ public class ReportManager {
|
||||
}
|
||||
|
||||
private static int parseDurationMinutes(String durationStr) {
|
||||
if (durationStr == null) return 5;
|
||||
|
||||
String s = durationStr.trim().toLowerCase(Locale.UK);
|
||||
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;
|
||||
int seconds = parseDurationSeconds(durationStr);
|
||||
if (seconds <= 0) return DEFAULT_MARKER_DURATION_SECS / 60;
|
||||
return Math.max(1, (int) Math.ceil(seconds / 60.0));
|
||||
}
|
||||
|
||||
private static String getStatusColor(int status) {
|
||||
@@ -271,7 +481,7 @@ public class ReportManager {
|
||||
SimpleDateFormat monthFormat = new SimpleDateFormat("MMMM yyyy", 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, Integer> eventsByMonth = new HashMap<>();
|
||||
for (SeizureGroup g : groups) {
|
||||
@@ -291,38 +501,41 @@ public class ReportManager {
|
||||
Calendar todayCal = Calendar.getInstance();
|
||||
Date today = todayCal.getTime();
|
||||
|
||||
// Build a fixed list of recent full months so the dropdown remains useful across
|
||||
// month boundaries, for example allowing April to be selected after May begins.
|
||||
Calendar firstMonth = Calendar.getInstance();
|
||||
firstMonth.add(Calendar.MONTH, -(MONTHS_TO_SHOW - 1));
|
||||
firstMonth.set(Calendar.DAY_OF_MONTH, 1);
|
||||
firstMonth.set(Calendar.HOUR_OF_DAY, 0);
|
||||
firstMonth.set(Calendar.MINUTE, 0);
|
||||
firstMonth.set(Calendar.SECOND, 0);
|
||||
firstMonth.set(Calendar.MILLISECOND, 0);
|
||||
// Build the selector from months that actually have event data. Newest months are
|
||||
// shown first. The current month is selected only if it has events; otherwise the
|
||||
// latest available month is selected.
|
||||
ArrayList<String> monthIds = new ArrayList<>(eventsByMonth.keySet());
|
||||
Collections.sort(monthIds);
|
||||
Collections.reverse(monthIds);
|
||||
|
||||
Calendar lastMonth = Calendar.getInstance();
|
||||
lastMonth.set(Calendar.DAY_OF_MONTH, 1);
|
||||
lastMonth.set(Calendar.HOUR_OF_DAY, 0);
|
||||
lastMonth.set(Calendar.MINUTE, 0);
|
||||
lastMonth.set(Calendar.SECOND, 0);
|
||||
lastMonth.set(Calendar.MILLISECOND, 0);
|
||||
String currentMonthId = monthIdFormat.format(today);
|
||||
String defaultMonthId = monthIds.contains(currentMonthId)
|
||||
? currentMonthId
|
||||
: (monthIds.isEmpty() ? currentMonthId : monthIds.get(0));
|
||||
|
||||
ArrayList<Calendar> reportMonths = new ArrayList<>();
|
||||
Calendar monthCursor = (Calendar) firstMonth.clone();
|
||||
while (!monthCursor.after(lastMonth)) {
|
||||
reportMonths.add((Calendar) monthCursor.clone());
|
||||
monthCursor.add(Calendar.MONTH, 1);
|
||||
for (String monthId : monthIds) {
|
||||
try {
|
||||
Date monthDate = monthIdFormat.parse(monthId);
|
||||
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();
|
||||
sb.append("<!DOCTYPE html><html><head>");
|
||||
sb.append("<meta charset='UTF-8'>");
|
||||
sb.append("<meta name='viewport' content='width=device-width, initial-scale=1'>");
|
||||
sb.append("<title>ClinX02 Seizure Report</title>");
|
||||
sb.append("<style>");
|
||||
sb.append("<title>FLOGA Seizure Report</title>");
|
||||
sb.append("<style>");
|
||||
sb.append("* { box-sizing: border-box; }");
|
||||
sb.append("body { font-family: Arial, sans-serif; margin: 0; padding: 16px; background: #eef1f5; color: #222; }");
|
||||
sb.append(".container { max-width: 1100px; margin: 0 auto; }");
|
||||
@@ -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-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.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-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; }");
|
||||
@@ -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(".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(".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(".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 td { padding: 10px 12px; border-bottom: 1px solid #eee; font-size: 13px; vertical-align: top; }");
|
||||
sb.append(".detail-table tr:hover { background: #f9fafb; }");
|
||||
@@ -410,31 +623,31 @@ public class ReportManager {
|
||||
sb.append(".timeline { height: 22px; }");
|
||||
sb.append(".event-bar { top: 4px; height: 14px; }");
|
||||
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("}");
|
||||
sb.append("</style></head><body>");
|
||||
sb.append("<div class='container'>");
|
||||
|
||||
sb.append("<h1>ClinX02 Seizure Report</h1>");
|
||||
sb.append("<p class='subtitle'>Calendar timeline overview and detailed event log</p>");
|
||||
sb.append("<h1>FLOGA Seizure Report</h1>");
|
||||
sb.append("<p class='subtitle'>Calendar timeline overview and detailed alarm log</p>");
|
||||
|
||||
// Summary box
|
||||
sb.append("<div class='summary'>");
|
||||
sb.append("<div class='stats'>");
|
||||
|
||||
sb.append("<div class='stat-card'>");
|
||||
sb.append("<div class='stat-label'>Calendar range</div>");
|
||||
sb.append("<div class='stat-value'>").append(MONTHS_TO_SHOW).append(" months</div>");
|
||||
sb.append("<div class='stat-label'>Months with events</div>");
|
||||
sb.append("<div class='stat-value'>").append(eventsByMonth.size()).append("</div>");
|
||||
sb.append("</div>");
|
||||
|
||||
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>");
|
||||
|
||||
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>");
|
||||
|
||||
@@ -443,18 +656,6 @@ public class ReportManager {
|
||||
.append("&token=clinx02secure' download='seizure_report.html'>Download Report</a>");
|
||||
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
|
||||
sb.append("<div class='month-controls'>");
|
||||
sb.append("<label for='monthSelect'>Month:</label>");
|
||||
@@ -466,7 +667,7 @@ public class ReportManager {
|
||||
int count = eventCount == null ? 0 : eventCount;
|
||||
sb.append("<option value='").append(monthId).append("'").append(selected).append(">");
|
||||
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("</select>");
|
||||
@@ -505,7 +706,7 @@ public class ReportManager {
|
||||
sb.append("<div class='month-summary-panel");
|
||||
if (activeMonth) sb.append(" active");
|
||||
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, "Morning", "06-12", "morning", morningCount, maxTimeCount);
|
||||
appendSummaryBar(sb, "Afternoon", "12-18", "afternoon", afternoonCount, maxTimeCount);
|
||||
@@ -513,6 +714,16 @@ public class ReportManager {
|
||||
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
|
||||
for (Calendar reportMonth : reportMonths) {
|
||||
String monthId = monthIdFormat.format(reportMonth.getTime());
|
||||
@@ -549,7 +760,7 @@ public class ReportManager {
|
||||
|
||||
String boxClass = "day-box";
|
||||
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='day-num'>").append(day).append("</div>");
|
||||
@@ -570,9 +781,12 @@ public class ReportManager {
|
||||
|
||||
for (SeizureGroup g : dayGroups) {
|
||||
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);
|
||||
|
||||
if (leftPct + widthPct > 100.0) {
|
||||
@@ -597,8 +811,8 @@ public class ReportManager {
|
||||
|
||||
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='seizure-count'>").append(dayGroups.size())
|
||||
.append(dayGroups.size() == 1 ? " event" : " events").append("</div>");
|
||||
sb.append("<div class='alarm-count'>").append(dayGroups.size())
|
||||
.append(dayGroups.size() == 1 ? " alarm" : " alarms").append("</div>");
|
||||
}
|
||||
|
||||
sb.append("</div>");
|
||||
@@ -609,19 +823,18 @@ public class ReportManager {
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
sb.append("<p>No seizure events recorded in this period.</p>");
|
||||
sb.append("<p>No alarm events recorded in this period.</p>");
|
||||
} 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><label for='rowsPerPage'>Entries per page: </label>");
|
||||
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='25'>25</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("<div class='pager'>");
|
||||
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>");
|
||||
sb.append("<div class='table-wrap' id='eventTableWrap'><table class='detail-table'>");
|
||||
sb.append("<tr><th>#</th><th>Date & Time</th><th>Status</th><th>Duration</th><th>Heart Rate</th><th>Cause</th></tr>");
|
||||
sb.append("<tr><th>#</th><th>Date & 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);
|
||||
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 statusStr = statusToString(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 cause = g.firstEvent.alarmCause != null && !g.firstEvent.alarmCause.isEmpty()
|
||||
? g.firstEvent.alarmCause : "Unknown";
|
||||
String hrStr = g.hr > 0 ? String.format(Locale.UK, "%d bpm", Math.round(g.hr)) : "N/A";
|
||||
String dotColor = getTimeOfDayColor(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>").append(escapeHtml(g.durationStr)).append("</td>");
|
||||
sb.append("<td>").append(hrStr).append("</td>");
|
||||
sb.append("<td>").append(escapeHtml(cause)).append("</td>");
|
||||
sb.append("</tr>");
|
||||
}
|
||||
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("<script>");
|
||||
@@ -754,7 +965,9 @@ public class ReportManager {
|
||||
String alarmPhrase;
|
||||
String alarmCause;
|
||||
double hr = 0.0;
|
||||
int durationSeconds = 0;
|
||||
Date date;
|
||||
Date inferredStartDate;
|
||||
}
|
||||
|
||||
static class SeizureGroup {
|
||||
@@ -762,18 +975,53 @@ public class ReportManager {
|
||||
SeizureEvent lastEvent;
|
||||
Date startDate;
|
||||
Date lastDate;
|
||||
Date inferredStartDate;
|
||||
String durationStr = "N/A";
|
||||
int durationSeconds = 0;
|
||||
double hr = 0.0;
|
||||
boolean hasDurationEvidence = false;
|
||||
|
||||
SeizureGroup(SeizureEvent first) {
|
||||
this.firstEvent = first;
|
||||
this.lastEvent = first;
|
||||
this.startDate = first.date;
|
||||
this.startDate = first.inferredStartDate != null ? first.inferredStartDate : first.date;
|
||||
this.lastDate = first.date;
|
||||
if (first.notes != null && first.notes.contains("Duration:")) {
|
||||
this.durationStr = extractDuration(first.notes);
|
||||
this.inferredStartDate = first.inferredStartDate;
|
||||
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;
|
||||
}
|
||||
|
||||
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.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Based on example at:
|
||||
@@ -127,11 +128,14 @@ public class SdServer extends Service implements SdDataReceiver {
|
||||
private boolean mSMSAlarm = false;
|
||||
// Emergency-services escalation 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 mEmergencySmsNotifyContacts = true;
|
||||
private String mEmergencySmsNumber = "";
|
||||
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 boolean mEmergencySmsSent = false;
|
||||
|
||||
@@ -441,6 +445,8 @@ public class SdServer extends Service implements SdDataReceiver {
|
||||
// Stop the Cancel Alarm Latch timer
|
||||
Log.d(TAG, "onDestroy(): stopping alarm latch timer");
|
||||
stopLatchTimer();
|
||||
// Stop the Emergency SMS timer
|
||||
stopEmergencySmsTimer();
|
||||
|
||||
|
||||
// Stop the location finder.
|
||||
@@ -636,6 +642,9 @@ public class SdServer extends Service implements SdDataReceiver {
|
||||
sdData.alarmPhrase = "OK";
|
||||
sdData.alarmStanding = false;
|
||||
sdData.fallAlarmStanding = false;
|
||||
|
||||
resetEmergencySmsState();
|
||||
|
||||
showNotification(0);
|
||||
}
|
||||
}
|
||||
@@ -644,6 +653,9 @@ public class SdServer extends Service implements SdDataReceiver {
|
||||
sdData.alarmPhrase = "MUTE";
|
||||
sdData.alarmStanding = false;
|
||||
sdData.fallAlarmStanding = false;
|
||||
|
||||
resetEmergencySmsState();
|
||||
|
||||
showNotification(0);
|
||||
}
|
||||
// Handle warning alarm state
|
||||
@@ -1102,6 +1114,11 @@ public class SdServer extends Service implements SdDataReceiver {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mEmergencySmsDelaySecs <= 0) {
|
||||
Log.w(TAG, "startEmergencySmsTimer() - invalid delay, using 300 seconds");
|
||||
mEmergencySmsDelaySecs = 300;
|
||||
}
|
||||
|
||||
Log.i(TAG, "startEmergencySmsTimer() - starting emergency SMS timer for "
|
||||
+ 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() {
|
||||
if (mEmergencySmsTimer != null) {
|
||||
Log.i(TAG, "stopEmergencySmsTimer() - cancelling emergency SMS timer");
|
||||
@@ -1163,13 +1189,11 @@ public class SdServer extends Service implements SdDataReceiver {
|
||||
*/
|
||||
public void acceptAlarm() {
|
||||
Log.i(TAG, "acceptAlarm()");
|
||||
|
||||
mSdData.alarmStanding = false;
|
||||
mSdData.fallAlarmStanding = false;
|
||||
mAlarmStartTime = 0;
|
||||
mLastAlarmDateStr = "";
|
||||
mEmergencySmsSent = false;
|
||||
|
||||
stopEmergencySmsTimer();
|
||||
resetEmergencySmsState();
|
||||
|
||||
mSdDataSource.acceptAlarm();
|
||||
stopLatchTimer();
|
||||
@@ -1618,11 +1642,24 @@ public class SdServer extends Service implements SdDataReceiver {
|
||||
return;
|
||||
}
|
||||
|
||||
if (msgStr == null) {
|
||||
Log.w(TAG, "sendSMSDirect() - null message, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "sendSMSDirect() - Sending to " + number);
|
||||
Log.i(TAG, "sendSMSDirect() - message length = " + msgStr.length());
|
||||
|
||||
try {
|
||||
SmsManager sm = SmsManager.getDefault();
|
||||
sm.sendTextMessage(number, null, msgStr, null, null);
|
||||
|
||||
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);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "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
|
||||
+ alarmTimePart
|
||||
+ " Alarm duration: "
|
||||
+ " Duration: "
|
||||
+ mEmergencySmsDelaySecs
|
||||
+ " seconds."
|
||||
+ " sec."
|
||||
+ locationPart
|
||||
+ " Device ID: "
|
||||
+ " Device: "
|
||||
+ shortUuidStr;
|
||||
Log.i(TAG, "sendEmergencySms() - emergency message length = " + emergencyMessage.length());
|
||||
|
||||
Log.i(TAG, "sendEmergencySms() - sending emergency SMS to " + mEmergencySmsNumber);
|
||||
Log.i(TAG, "sendEmergencySms() - message: " + emergencyMessage);
|
||||
|
||||
sendSMSDirect(mEmergencySmsNumber, emergencyMessage);
|
||||
sendSMSDirect(mEmergencySmsNumber.trim(), emergencyMessage);
|
||||
|
||||
if (mEmergencySmsNotifyContacts) {
|
||||
String contactMessage = "Emergency services have been contacted for seizure alert"
|
||||
String contactMessage = "Emergency services have been contacted for seizure alert."
|
||||
+ alarmTimePart
|
||||
+ locationPart
|
||||
+ " Device: "
|
||||
@@ -1780,8 +1818,8 @@ public class SdServer extends Service implements SdDataReceiver {
|
||||
return;
|
||||
}
|
||||
|
||||
sendEmergencySms();
|
||||
mEmergencySmsSent = true;
|
||||
sendEmergencySms();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
Reference in New Issue
Block a user