Compare commits

...

8 commits

Author SHA1 Message Date
a27b8713a9 A little bit of extra logging. 2023-12-03 21:09:23 -08:00
42311b446b Show reason for dead state on notification. 2023-11-30 22:41:10 -08:00
4e2beedaef Rename gave_up_at to dead_at. 2023-11-30 22:40:38 -08:00
33405cd063 Reload the database when resuming full_power.
I had an incident where I was tampering with the database on my
computer, being synced over Syncthing, and I caused a conflict that
made the app stop recording, even though the app UI continued to show
the normal recording icons. Closing and re-opening the db connection
when leaving the house would have helped prevent that.
2023-11-30 17:43:46 -08:00
37c5754a24 Go to dead state if app tries to record without database ready. 2023-11-30 17:41:57 -08:00
b84185ea4e Escape ampersands and angle brackets from track title in GPX. 2023-11-30 17:41:21 -08:00
52dbae2f41 Remove commit parameter, do it on the caller side. 2023-11-30 17:41:00 -08:00
0fc6971d9d Show database ready time in debug info. 2023-11-30 17:38:23 -08:00
6 changed files with 69 additions and 62 deletions

View file

@ -9,23 +9,27 @@ import java.io.File
class Database(val trackbook: Trackbook)
{
var ready: Boolean = false
var ready_at: Long = 0
lateinit var file: File
lateinit var connection: SQLiteDatabase
fun close()
{
Log.i("VOUSSOIR", "Database.close")
this.connection.close()
this.ready = false
this.ready_at = 0
this.trackbook.call_database_changed_listeners()
}
fun connect(file: File)
{
Log.i("VOUSSOIR", "Connecting to database " + file.absolutePath)
Log.i("VOUSSOIR", "Database.connect: " + file.absolutePath)
this.file = file
this.connection = openOrCreateDatabase(file, null)
this.initialize_tables()
this.ready = true
this.ready_at = System.currentTimeMillis()
Log.i("VOUSSOIR", "Database.open: Calling all listeners")
}
@ -52,29 +56,21 @@ class Database(val trackbook: Trackbook)
this.connection.endTransaction()
}
fun delete_trkpt(device_id: String, time: Long, commit: Boolean=false)
fun delete_trkpt(device_id: String, time: Long)
{
Log.i("VOUSSOIR", "Database.delete_trkpt")
begin_transaction()
connection.delete("trkpt", "device_id = ? AND time = ?", arrayOf(device_id, time.toString()))
if (commit)
{
this.commit()
}
}
fun delete_trkpt_start_end(device_id: String, start_time: Long, end_time: Long, commit: Boolean=false)
fun delete_trkpt_start_end(device_id: String, start_time: Long, end_time: Long)
{
Log.i("VOUSSOIR", "Track.delete ${device_id} ${start_time} -- ${end_time}.")
this.begin_transaction()
this.connection.delete("trkpt", "device_id = ? AND time >= ? AND time <= ?", arrayOf(device_id, start_time.toString(), end_time.toString()))
if (commit)
{
this.commit()
}
}
fun insert_trkpt(trkpt: Trkpt, commit: Boolean=false)
fun insert_trkpt(trkpt: Trkpt)
{
Log.i("VOUSSOIR", "Database.insert_trkpt")
val values = ContentValues().apply {
@ -89,10 +85,6 @@ class Database(val trackbook: Trackbook)
}
begin_transaction()
connection.insert("trkpt", null, values)
if (commit)
{
this.commit()
}
}
fun select_trkpt_start_end(device_id: String, start_time: Long, end_time: Long, max_accuracy: Float=Keys.DEFAULT_MAX_ACCURACY, order: String="ASC"): Iterator<Trkpt>
@ -156,18 +148,14 @@ class Database(val trackbook: Trackbook)
}
}
fun delete_homepoint(id: Long, commit: Boolean=false)
fun delete_homepoint(id: Long)
{
Log.i("VOUSSOIR", "Database.delete_homepoint")
begin_transaction()
connection.delete("homepoints", "id = ?", arrayOf(id.toString()))
if (commit)
{
this.commit()
}
}
fun insert_homepoint(id: Long, name: String, latitude: Double, longitude: Double, radius: Double, commit: Boolean=false)
fun insert_homepoint(id: Long, name: String, latitude: Double, longitude: Double, radius: Double)
{
Log.i("VOUSSOIR", "Database.insert_homepoint")
val values = ContentValues().apply {
@ -179,13 +167,9 @@ class Database(val trackbook: Trackbook)
}
begin_transaction()
connection.insert("homepoints", null, values)
if (commit)
{
this.commit()
}
}
fun update_homepoint(id: Long, name: String, radius: Double, commit: Boolean=false)
fun update_homepoint(id: Long, name: String, radius: Double)
{
Log.i("VOUSSOIR", "Database.update_homepoint")
val values = ContentValues().apply {
@ -194,13 +178,9 @@ class Database(val trackbook: Trackbook)
}
begin_transaction()
connection.update("homepoints", values, "id = ?", arrayOf(id.toString()))
if (commit)
{
this.commit()
}
}
fun update_trkpt(trkpt: Trkpt, commit: Boolean=false)
fun update_trkpt(trkpt: Trkpt)
{
Log.i("VOUSSOIR", "Database.update_trkpt")
val values = ContentValues().apply {
@ -213,14 +193,11 @@ class Database(val trackbook: Trackbook)
}
begin_transaction()
connection.update("trkpt", values, "device_id = ? AND time = ?", arrayOf(trkpt.device_id, trkpt.time.toString()))
if (commit)
{
this.commit()
}
}
private fun initialize_tables()
{
Log.i("VOUSSOIR", "Database.initialize_tables")
begin_transaction()
this.connection.execSQL("CREATE TABLE IF NOT EXISTS meta(name TEXT PRIMARY KEY, value TEXT)")
this.connection.execSQL("CREATE TABLE IF NOT EXISTS trkpt(device_id INTEGER NOT NULL, time INTEGER NOT NULL, lat REAL NOT NULL, lon REAL NOT NULL, provider TEXT, accuracy REAL, ele INTEGER, sat INTEGER, PRIMARY KEY(device_id, time))")

View file

@ -168,8 +168,8 @@ class MapFragment : Fragment()
latitude=point.latitude,
longitude=point.longitude,
radius=radius,
commit=true,
)
trackbook.database.commit()
trackbook.load_homepoints()
create_homepoint_overlays()
dialog.dismiss()
@ -548,14 +548,16 @@ class MapFragment : Fragment()
val save_button: Button = dialog.findViewById(R.id.homepoint_save_button)
delete_button.text = "Delete"
delete_button.setOnClickListener {
trackbook.database.delete_homepoint(homepoint.id, commit=true)
trackbook.database.delete_homepoint(homepoint.id)
trackbook.database.commit()
trackbook.load_homepoints()
create_homepoint_overlays()
dialog.dismiss()
}
save_button.setOnClickListener {
val radius = radius_input.text.toString().toDoubleOrNull() ?: 25.0
trackbook.database.update_homepoint(homepoint.id, name=name_input.text.toString(), radius=radius, commit=true)
trackbook.database.update_homepoint(homepoint.id, name=name_input.text.toString(), radius=radius)
trackbook.database.commit()
trackbook.load_homepoints()
create_homepoint_overlays()
dialog.dismiss()
@ -670,11 +672,12 @@ class MapFragment : Fragment()
state: ${state_name()}
clock: ${iso8601_local_noms(System.currentTimeMillis())}
location: ${iso8601_local_noms(tracker.currentBestLocation.time)}
database: ${iso8601_local_noms(trackbook.database.ready_at)}
listeners: ${iso8601_local_noms(tracker.listeners_enabled_at)}
motion: ${iso8601_local_noms(tracker.last_significant_motion)}
watchdog: ${iso8601_local_noms(tracker.last_watchdog)}
home: ${iso8601_local_noms(tracker.arrived_at_home)}
died: ${iso8601_local_noms(tracker.gave_up_at)}
died: ${iso8601_local_noms(tracker.dead_at)}
power: ${tracker.device_is_charging}
wakelock: ${tracker.wakelock.isHeld}
accuracy: ${accuracy_text} m

View file

@ -97,8 +97,9 @@ data class Track (
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
>
""".trimIndent())
val name_escaped = this.name.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
write("\t<metadata>")
write("\t\t<name>${this.name}</name>")
write("\t\t<name>${name_escaped}</name>")
write("\t\t<application>${BuildConfig.APPLICATION_ID} ${BuildConfig.VERSION_NAME}</application>")
write("\t\t<device>${this.device_id}</device>")
write("\t</metadata>")

View file

@ -271,7 +271,8 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener
Log.i("VOUSSOIR", selected.rendered_by_polyline?.actualPoints?.size.toString())
selected.rendered_by_polyline?.setPoints(ArrayList(selected.rendered_by_polyline?.actualPoints))
Log.i("VOUSSOIR", selected.rendered_by_polyline?.actualPoints?.size.toString())
trackbook.database.delete_trkpt(selected.device_id, selected.time, commit=true)
trackbook.database.delete_trkpt(selected.device_id, selected.time)
trackbook.database.commit()
deselect_trkpt()
mapView.invalidate()
}
@ -454,7 +455,10 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener
}
override fun onMarkerDragEnd(marker: Marker?)
{
selected_trkpt?.let { trackbook.database.update_trkpt(it, commit=true) }
selected_trkpt?.let {
trackbook.database.update_trkpt(it)
trackbook.database.commit()
}
}
})
@ -631,7 +635,8 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener
mid_location.accuracy = 0f
val mid_trkpt = Trkpt(trkpt.device_id, mid_location)
deselect_trkpt()
trackbook.database.insert_trkpt(mid_trkpt, commit=true)
trackbook.database.insert_trkpt(mid_trkpt)
trackbook.database.commit()
requery_and_render.run()
select_trkpt_by_timestamp(mid_trkpt.time)
return
@ -695,7 +700,7 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener
trkpt.latitude = new_location.latitude
trkpt.longitude = new_location.longitude
trkpt.altitude = a.altitude + (ele_span * proportion)
trackbook.database.update_trkpt(trkpt, commit=false)
trackbook.database.update_trkpt(trkpt)
}
trackbook.database.commit()
}
@ -862,8 +867,8 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener
track.device_id,
track.trkpts.first().time,
track.trkpts.last().time,
commit=true,
)
trackbook.database.commit()
handler.removeCallbacks(requery_and_render)
handler.postDelayed(requery_and_render, RERENDER_DELAY)
}

View file

@ -68,19 +68,22 @@ class Trackbook : Application()
{
Log.i("VOUSSOIR", "Trackbook.load_database")
val folder = PreferencesHelper.load_database_folder()
this.database.commit()
if (this.database.ready)
{
Log.i("VOUSSOIR", "Trackbook.load_database: closing and re-opening.")
this.database.commit()
this.database.close()
}
if (folder == "")
{
Log.i("VOUSSOIR", "Trackbook.load_database: folder came up blank.")
this.database.ready = false
return
}
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)
{
this.database.connect(File(folder + "/trkpt_${PreferencesHelper.load_device_id()}.db"))
val device_id = PreferencesHelper.load_device_id()
this.database.connect(File(folder + "/trkpt_${device_id}.db"))
this.load_homepoints()
}
else

View file

@ -56,7 +56,8 @@ class TrackerService: Service()
var listeners_enabled_at: Long = 0
var last_significant_motion: Long = 0
var last_watchdog: Long = 0
var gave_up_at: Long = 0
var dead_at: Long = 0
var dead_reason: String = ""
var arrived_at_home: Long = 0
val TIME_UNTIL_SLEEP: Long = 5 * Keys.ONE_MINUTE_IN_MILLISECONDS
val TIME_UNTIL_DEAD: Long = 3 * Keys.ONE_MINUTE_IN_MILLISECONDS
@ -185,7 +186,7 @@ class TrackerService: Service()
Keys.STATE_FULL_RECORDING -> state_full_recording()
Keys.STATE_ARRIVED_AT_HOME -> state_arrived_at_home()
Keys.STATE_SLEEP -> state_sleep()
Keys.STATE_DEAD -> state_dead()
Keys.STATE_DEAD -> state_dead(dead_reason="Loaded previous state")
}
}
@ -200,7 +201,8 @@ class TrackerService: Service()
trackbook.database.commit()
recent_displacement_locations.clear()
arrived_at_home = 0
gave_up_at = 0
dead_at = 0
dead_reason = ""
if (foreground_started > 0)
{
stopForeground(STOP_FOREGROUND_DETACH)
@ -215,11 +217,17 @@ class TrackerService: Service()
// at full power. A wakelock is used to resist Android's doze. This state should be active
// while out and about.
Log.i("VOUSSOIR", "TrackerService.state_full_power")
if (! trackbook.database.ready)
{
state_dead("Database not ready")
}
trackbook.load_database()
tracking_state = Keys.STATE_FULL_RECORDING
PreferencesHelper.saveTrackingState(tracking_state)
reset_location_listeners(Keys.LOCATION_INTERVAL_FULL_POWER)
arrived_at_home = 0
gave_up_at = 0
dead_at = 0
dead_reason = ""
if (foreground_started == 0L)
{
startForeground(Keys.TRACKER_SERVICE_NOTIFICATION_ID, displayNotification())
@ -231,7 +239,7 @@ class TrackerService: Service()
}
else
{
state_dead()
state_dead("No listeners enabled")
}
displayNotification()
}
@ -245,7 +253,8 @@ class TrackerService: Service()
reset_location_listeners(Keys.LOCATION_INTERVAL_FULL_POWER)
trackbook.database.commit()
arrived_at_home = System.currentTimeMillis()
gave_up_at = 0
dead_at = 0
dead_reason = ""
stop_wakelock()
displayNotification()
}
@ -258,11 +267,12 @@ class TrackerService: Service()
PreferencesHelper.saveTrackingState(tracking_state)
reset_location_listeners(Keys.LOCATION_INTERVAL_SLEEP)
arrived_at_home = arrived_at_home
gave_up_at = 0
dead_at = 0
dead_reason = ""
stop_wakelock()
displayNotification()
}
fun state_dead()
fun state_dead(dead_reason: String)
{
// This state is activated when the device is struggling to receive a GPS fix due to being
// indoors / underground. It will be woken up again by the accelerometers or by plugging /
@ -274,7 +284,8 @@ class TrackerService: Service()
trackbook.database.commit()
recent_displacement_locations.clear()
arrived_at_home = 0
gave_up_at = System.currentTimeMillis()
dead_at = System.currentTimeMillis()
this.dead_reason = dead_reason
stop_wakelock()
displayNotification()
}
@ -287,12 +298,13 @@ class TrackerService: Service()
PreferencesHelper.saveTrackingState(tracking_state)
reset_location_listeners(Keys.LOCATION_INTERVAL_FULL_POWER)
arrived_at_home = 0
gave_up_at = 0
dead_at = 0
dead_reason = ""
stop_wakelock()
displayNotification()
if (!gpsLocationListenerRegistered && !networkLocationListenerRegistered)
{
state_dead()
state_dead(dead_reason="No listeners enabled")
}
}
@ -365,7 +377,8 @@ class TrackerService: Service()
if(! trackbook.database.ready)
{
Log.i("VOUSSOIR", "Omitting due to database not ready.")
Log.i("VOUSSOIR", "TrackerService.onLocationChanged: database is not ready!!.")
state_dead(dead_reason="Database not ready")
return
}
@ -449,7 +462,7 @@ class TrackerService: Service()
}
val trkpt = Trkpt(device_id=device_id, location=location)
trackbook.database.insert_trkpt(trkpt, commit=false)
trackbook.database.insert_trkpt(trkpt)
if (trkpt.accuracy <= max_accuracy)
{
@ -503,26 +516,31 @@ class TrackerService: Service()
if (tracking_state == Keys.STATE_FULL_RECORDING)
{
notification_builder.setContentTitle("${timestamp} (recording)")
notification_builder.setContentText(null)
notification_builder.setSmallIcon(R.drawable.ic_satellite_24dp)
}
else if (tracking_state == Keys.STATE_ARRIVED_AT_HOME)
{
notification_builder.setContentTitle("${timestamp} (home)")
notification_builder.setContentText(null)
notification_builder.setSmallIcon(R.drawable.ic_homepoint_24dp)
}
else if (tracking_state == Keys.STATE_SLEEP)
{
notification_builder.setContentTitle("${timestamp} (sleeping)")
notification_builder.setContentText(null)
notification_builder.setSmallIcon(R.drawable.ic_sleep_24dp)
}
else if (tracking_state == Keys.STATE_DEAD)
{
notification_builder.setContentTitle("${timestamp} (dead)")
notification_builder.setContentText(dead_reason)
notification_builder.setSmallIcon(R.drawable.ic_skull_24dp)
}
else if (tracking_state == Keys.STATE_STOP || tracking_state == Keys.STATE_MAPVIEW)
{
notification_builder.setContentTitle("${timestamp} (stopped)")
notification_builder.setContentText(null)
notification_builder.setSmallIcon(R.drawable.ic_fiber_manual_stop_24dp)
}
@ -832,7 +850,7 @@ class TrackerService: Service()
(now - last_significant_motion) > TIME_UNTIL_DEAD
)
{
state_dead()
state_dead(dead_reason="No GPS reception")
}
}
}