Rewrite TrackerService to be more like a state machine.

master
voussoir 2023-04-30 15:14:05 -07:00
parent 2e5be6a4e4
commit 3710755bc1
11 changed files with 280 additions and 332 deletions

View File

@ -21,7 +21,7 @@ The goal of this fork is to make 24/7 recording easier. I want to be able to run
trkpt has three states of power management. The states transition like this: trkpt has three states of power management. The states transition like this:
1. **FULL POWER**: receives location updates as fast as Android provides them. A wakelock is used to prevent doze. 1. **FULL POWER**: receives location updates as fast as Android provides them. A wakelock is used to resist doze (though I think some devices will doze anyway because they do not respect the user).
Stay near homepoint for a few minutes → Sleep Stay near homepoint for a few minutes → Sleep

View File

@ -10,8 +10,8 @@ android {
applicationId 'net.voussoir.trkpt' applicationId 'net.voussoir.trkpt'
minSdkVersion 25 minSdkVersion 25
targetSdk 32 targetSdk 32
versionCode 54 versionCode 55
versionName '1.0.3' versionName '1.1.0'
resConfigs "en", "da", "de", "fr", "hr", "id", "it", "ja", "nb-rNO", "nl", "pl", "pt-rBR", "ru", "sv", "tr", "zh-rCN" resConfigs "en", "da", "de", "fr", "hr", "id", "it", "ja", "nb-rNO", "nl", "pl", "pt-rBR", "ru", "sv", "tr", "zh-rCN"
} }

View File

@ -53,18 +53,6 @@
</intent-filter> </intent-filter>
</service> </service>
<!-- TRACKING TOGGLE SERVICE SYSTEM QUICK SETTINGS -->
<service
android:name="net.voussoir.trkpt.TrackingToggleTileService"
android:label="@string/quick_settings_tile_title_default"
android:icon="@drawable/ic_notification_icon_small_24dp"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
android:exported="true">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
<!-- FILE PROVIDER GPX --> <!-- FILE PROVIDER GPX -->
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"

View File

@ -40,7 +40,6 @@ object Keys {
// args // args
const val ARG_TRACK_TITLE: String = "ArgTrackTitle" const val ARG_TRACK_TITLE: String = "ArgTrackTitle"
const val ARG_TRACK_ID: String = "ArgTrackID"
const val ARG_TRACK_DEVICE_ID: String = "ArgTrackDeviceID" const val ARG_TRACK_DEVICE_ID: String = "ArgTrackDeviceID"
const val ARG_TRACK_START_TIME: String = "ArgTrackStartTime" const val ARG_TRACK_START_TIME: String = "ArgTrackStartTime"
const val ARG_TRACK_STOP_TIME: String = "ArgTrackStopTime" const val ARG_TRACK_STOP_TIME: String = "ArgTrackStopTime"
@ -67,12 +66,15 @@ object Keys {
const val PREF_MAX_ACCURACY: String = "prefMaxAccuracy" const val PREF_MAX_ACCURACY: String = "prefMaxAccuracy"
// states // states
const val STATE_TRACKING_STOPPED: Int = 0 const val STATE_STOP: Int = 0
const val STATE_TRACKING_ACTIVE: Int = 1 const val STATE_FULL_RECORDING: Int = 1
const val STATE_ARRIVED_AT_HOME: Int = 2
const val STATE_SLEEP: Int = 3
const val STATE_DEAD: Int = 4
const val STATE_MAPVIEW: Int = 5
const val LOCATION_INTERVAL_FULL_POWER: Long = 0 const val LOCATION_INTERVAL_FULL_POWER: Long = 0
const val LOCATION_INTERVAL_SLEEP: Long = ONE_MINUTE_IN_MILLISECONDS const val LOCATION_INTERVAL_SLEEP: Long = ONE_MINUTE_IN_MILLISECONDS
const val LOCATION_INTERVAL_DEAD: Long = -1 const val LOCATION_INTERVAL_STOP: Long = -1
const val LOCATION_INTERVAL_STOP: Long = -2
const val STATE_THEME_FOLLOW_SYSTEM: String = "stateFollowSystem" const val STATE_THEME_FOLLOW_SYSTEM: String = "stateFollowSystem"
const val STATE_THEME_LIGHT_MODE: String = "stateLightMode" const val STATE_THEME_LIGHT_MODE: String = "stateLightMode"
const val STATE_THEME_DARK_MODE: String = "stateDarkMode" const val STATE_THEME_DARK_MODE: String = "stateDarkMode"

View File

@ -203,9 +203,9 @@ class MapFragment : Fragment()
{ {
return@setOnClickListener return@setOnClickListener
} }
if (tracker.trackingState == Keys.STATE_TRACKING_ACTIVE) if (tracker.tracking_state != Keys.STATE_STOP && tracker.tracking_state != Keys.STATE_MAPVIEW)
{ {
tracker.stopTracking() tracker.state_mapview()
} }
else else
{ {
@ -276,10 +276,10 @@ class MapFragment : Fragment()
return return
} }
saveBestLocationState(tracker.currentBestLocation) saveBestLocationState(tracker.currentBestLocation)
if (bound && tracker.trackingState != Keys.STATE_TRACKING_ACTIVE) if (bound && (tracker.tracking_state == Keys.STATE_MAPVIEW || tracker.tracking_state == Keys.STATE_STOP))
{ {
tracker.removeGpsLocationListener() tracker.remove_gps_location_listener()
tracker.removeNetworkLocationListener() tracker.remove_network_location_listener()
tracker.trackbook.database.commit() tracker.trackbook.database.commit()
} }
handler.removeCallbacks(redraw_runnable) handler.removeCallbacks(redraw_runnable)
@ -350,7 +350,7 @@ class MapFragment : Fragment()
} }
if (trackerService != null) if (trackerService != null)
{ {
trackerService!!.startTracking() trackerService!!.state_full_recording()
} }
} }
@ -413,19 +413,13 @@ class MapFragment : Fragment()
val newMarker: Drawable val newMarker: Drawable
val fillcolor: Int val fillcolor: Int
val description: String val description: String
if (tracker.listeners_enabled_at == 0L) if (tracker.tracking_state == Keys.STATE_DEAD)
{
fillcolor = Color.argb(64, 0, 0, 0)
newMarker = ContextCompat.getDrawable(requireContext(), R.drawable.ic_skull_24dp)!!
description = "No location listeners are enabled"
}
else if (tracker.trackingState == Keys.STATE_TRACKING_ACTIVE && tracker.location_interval == Keys.LOCATION_INTERVAL_DEAD)
{ {
fillcolor = Color.argb(64, 0, 0, 0) fillcolor = Color.argb(64, 0, 0, 0)
newMarker = ContextCompat.getDrawable(requireContext(), R.drawable.ic_skull_24dp)!! newMarker = ContextCompat.getDrawable(requireContext(), R.drawable.ic_skull_24dp)!!
description = "GPS is struggling; disabled until movement" description = "GPS is struggling; disabled until movement"
} }
else if (tracker.trackingState == Keys.STATE_TRACKING_ACTIVE && tracker.location_interval == Keys.LOCATION_INTERVAL_SLEEP) else if (tracker.tracking_state == Keys.STATE_SLEEP)
{ {
fillcolor = Color.argb(64, 220, 61, 51) fillcolor = Color.argb(64, 220, 61, 51)
newMarker = ContextCompat.getDrawable(requireContext(), R.drawable.ic_sleep_24dp)!! newMarker = ContextCompat.getDrawable(requireContext(), R.drawable.ic_sleep_24dp)!!
@ -437,7 +431,7 @@ class MapFragment : Fragment()
newMarker = ContextCompat.getDrawable(requireContext(), R.drawable.ic_marker_location_black_24dp)!! newMarker = ContextCompat.getDrawable(requireContext(), R.drawable.ic_marker_location_black_24dp)!!
description = "GPS tracking at full power" description = "GPS tracking at full power"
} }
else if (tracker.trackingState == Keys.STATE_TRACKING_ACTIVE) else if (tracker.tracking_state == Keys.STATE_FULL_RECORDING || tracker.tracking_state == Keys.STATE_ARRIVED_AT_HOME)
{ {
fillcolor = Color.argb(64, 220, 61, 51) fillcolor = Color.argb(64, 220, 61, 51)
newMarker = ContextCompat.getDrawable(requireContext(), R.drawable.ic_marker_location_red_24dp)!! newMarker = ContextCompat.getDrawable(requireContext(), R.drawable.ic_marker_location_red_24dp)!!
@ -591,13 +585,13 @@ class MapFragment : Fragment()
mainButton.text = requireContext().getString(R.string.button_not_ready) mainButton.text = requireContext().getString(R.string.button_not_ready)
mainButton.icon = null mainButton.icon = null
} }
else if (tracker == null || tracker.trackingState == Keys.STATE_TRACKING_STOPPED) else if (tracker == null || tracker.tracking_state == Keys.STATE_STOP || tracker.tracking_state == Keys.STATE_MAPVIEW)
{ {
mainButton.setIconResource(R.drawable.ic_fiber_manual_record_inactive_24dp) mainButton.setIconResource(R.drawable.ic_fiber_manual_record_inactive_24dp)
mainButton.text = requireContext().getString(R.string.button_start) mainButton.text = requireContext().getString(R.string.button_start)
mainButton.contentDescription = requireContext().getString(R.string.descr_button_start) mainButton.contentDescription = requireContext().getString(R.string.descr_button_start)
} }
else if (tracker.trackingState == Keys.STATE_TRACKING_ACTIVE) else
{ {
mainButton.setIconResource(R.drawable.ic_fiber_manual_stop_24dp) mainButton.setIconResource(R.drawable.ic_fiber_manual_stop_24dp)
mainButton.text = requireContext().getString(R.string.button_pause) mainButton.text = requireContext().getString(R.string.button_pause)
@ -672,11 +666,13 @@ class MapFragment : Fragment()
if (show_debug) if (show_debug)
{ {
map_current_time.text = """ map_current_time.text = """
state: ${state_name()}
now: ${iso8601_local_noms(System.currentTimeMillis())} now: ${iso8601_local_noms(System.currentTimeMillis())}
location: ${iso8601_local_noms(tracker.currentBestLocation.time)} location: ${iso8601_local_noms(tracker.currentBestLocation.time)}
listeners: ${iso8601_local_noms(tracker.listeners_enabled_at)} listeners: ${iso8601_local_noms(tracker.listeners_enabled_at)}
motion: ${iso8601_local_noms(tracker.last_significant_motion)} motion: ${iso8601_local_noms(tracker.last_significant_motion)}
watchdog: ${iso8601_local_noms(tracker.last_watchdog)} 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.gave_up_at)}
power: ${tracker.device_is_charging} power: ${tracker.device_is_charging}
wakelock: ${tracker.wakelock.isHeld} wakelock: ${tracker.wakelock.isHeld}
@ -690,6 +686,25 @@ class MapFragment : Fragment()
mapView.invalidate() mapView.invalidate()
} }
fun state_name(): String
{
val tracker = trackerService
if (tracker == null)
{
return "null"
}
return when (tracker.tracking_state)
{
Keys.STATE_STOP -> "stop"
Keys.STATE_FULL_RECORDING -> "recording"
Keys.STATE_ARRIVED_AT_HOME -> "home"
Keys.STATE_SLEEP -> "sleep"
Keys.STATE_DEAD -> "dead"
Keys.STATE_MAPVIEW -> "mapview"
else -> tracker.tracking_state.toString()
}
}
val redraw_runnable: Runnable = object : Runnable val redraw_runnable: Runnable = object : Runnable
{ {
override fun run() override fun run()

View File

@ -173,8 +173,8 @@ class TrackFragment : Fragment(), MapListener, YesNoDialog.YesNoDialogListener
mapView.zoomController.setVisibility(org.osmdroid.views.CustomZoomButtonsController.Visibility.NEVER) mapView.zoomController.setVisibility(org.osmdroid.views.CustomZoomButtonsController.Visibility.NEVER)
if (track.trkpts.size > 0) if (track.trkpts.size > 0)
{ {
val first = track.trkpts.first() val last = track.trkpts.last()
controller.setCenter(GeoPoint(first.latitude, first.longitude)) controller.setCenter(GeoPoint(last.latitude, last.longitude))
} }
controller.setZoom(Keys.DEFAULT_ZOOM_LEVEL) controller.setZoom(Keys.DEFAULT_ZOOM_LEVEL)

View File

@ -43,20 +43,20 @@ class TrackerService: Service()
lateinit var trackbook: Trackbook lateinit var trackbook: Trackbook
val handler: Handler = Handler(Looper.getMainLooper()) val handler: Handler = Handler(Looper.getMainLooper())
var trackingState: Int = Keys.STATE_TRACKING_STOPPED var tracking_state: Int = Keys.STATE_STOP
var useImperial: Boolean = false var useImperial: Boolean = false
var omitRests: Boolean = true var omitRests: Boolean = true
var max_accuracy: Float = Keys.DEFAULT_MAX_ACCURACY var max_accuracy: Float = Keys.DEFAULT_MAX_ACCURACY
var allow_sleep: Boolean = true var allow_sleep: Boolean = true
var device_id: String = random_device_id() var device_id: String = random_device_id()
var currentBestLocation: Location = getDefaultLocation() var currentBestLocation: Location = getDefaultLocation()
var lastCommit: Long = 0 var last_commit: Long = 0
var foreground_started: Long = 0
var listeners_enabled_at: Long = 0 var listeners_enabled_at: Long = 0
var last_significant_motion: Long = 0 var last_significant_motion: Long = 0
var last_watchdog: Long = 0 var last_watchdog: Long = 0
var gave_up_at: Long = 0 var gave_up_at: Long = 0
var arrived_at_home: Long = 0 var arrived_at_home: Long = 0
var location_interval: Long = 0
val TIME_UNTIL_SLEEP: Long = 5 * Keys.ONE_MINUTE_IN_MILLISECONDS val TIME_UNTIL_SLEEP: Long = 5 * Keys.ONE_MINUTE_IN_MILLISECONDS
val TIME_UNTIL_DEAD: Long = 3 * Keys.ONE_MINUTE_IN_MILLISECONDS val TIME_UNTIL_DEAD: Long = 3 * Keys.ONE_MINUTE_IN_MILLISECONDS
val WATCHDOG_INTERVAL: Long = 61 * Keys.ONE_SECOND_IN_MILLISECONDS val WATCHDOG_INTERVAL: Long = 61 * Keys.ONE_SECOND_IN_MILLISECONDS
@ -67,7 +67,7 @@ class TrackerService: Service()
var bound: Boolean = false var bound: Boolean = false
private val binder = TrackerServiceBinder(this) private val binder = TrackerServiceBinder(this)
private lateinit var notificationManager: NotificationManager private lateinit var notification_manager: NotificationManager
private lateinit var notification_builder: NotificationCompat.Builder private lateinit var notification_builder: NotificationCompat.Builder
private lateinit var locationManager: LocationManager private lateinit var locationManager: LocationManager
@ -90,7 +90,7 @@ class TrackerService: Service()
lateinit var wakelock: PowerManager.WakeLock lateinit var wakelock: PowerManager.WakeLock
private fun addGpsLocationListener(interval: Long): Boolean private fun add_gps_location_listener(interval: Long): Boolean
{ {
gpsLocationListenerRegistered = false gpsLocationListenerRegistered = false
gpsProviderActive = isGpsEnabled(locationManager) gpsProviderActive = isGpsEnabled(locationManager)
@ -118,7 +118,7 @@ class TrackerService: Service()
return true return true
} }
private fun addNetworkLocationListener(interval: Long): Boolean private fun add_network_location_listener(interval: Long): Boolean
{ {
networkLocationListenerRegistered = false networkLocationListenerRegistered = false
networkProviderActive = isNetworkEnabled(locationManager) networkProviderActive = isNetworkEnabled(locationManager)
@ -146,7 +146,7 @@ class TrackerService: Service()
return true return true
} }
fun removeGpsLocationListener() fun remove_gps_location_listener()
{ {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)
{ {
@ -160,7 +160,7 @@ class TrackerService: Service()
} }
} }
fun removeNetworkLocationListener() fun remove_network_location_listener()
{ {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)
{ {
@ -174,67 +174,171 @@ class TrackerService: Service()
} }
} }
fun load_tracking_state()
{
tracking_state = PreferencesHelper.loadTrackingState()
when (tracking_state)
{
Keys.STATE_STOP -> state_stop()
Keys.STATE_MAPVIEW -> state_mapview()
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()
}
}
fun state_stop()
{
// This state is activated when the user intentionally stops the recording, or exits the
// mapfragment without starting to record.
Log.i("VOUSSOIR", "TrackerService.state_stop")
tracking_state = Keys.STATE_STOP
PreferencesHelper.saveTrackingState(tracking_state)
reset_location_listeners(Keys.LOCATION_INTERVAL_STOP)
trackbook.database.commit()
recent_displacement_locations.clear()
arrived_at_home = 0
gave_up_at = 0
if (foreground_started > 0)
{
stopForeground(STOP_FOREGROUND_DETACH)
foreground_started = 0
}
stop_wakelock()
displayNotification()
}
fun state_full_recording()
{
// This state is the only one that will record points into the database, and tracks location
// 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")
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
if (foreground_started == 0L)
{
startForeground(Keys.TRACKER_SERVICE_NOTIFICATION_ID, displayNotification())
foreground_started = System.currentTimeMillis()
}
if (gpsLocationListenerRegistered || networkLocationListenerRegistered)
{
start_wakelock()
}
else
{
state_dead()
}
displayNotification()
}
fun state_arrived_at_home()
{
// This state is activated when the user enters the radius of a homepoint. The GPS will
// remain at full power for a few minutes before we transition to sleep.
Log.i("VOUSSOIR", "TrackerService.state_arrived_at_home")
tracking_state = Keys.STATE_ARRIVED_AT_HOME
PreferencesHelper.saveTrackingState(tracking_state)
reset_location_listeners(Keys.LOCATION_INTERVAL_FULL_POWER)
trackbook.database.commit()
arrived_at_home = System.currentTimeMillis()
gave_up_at = 0
stop_wakelock()
displayNotification()
}
fun state_sleep()
{
// This state is activated when the user stays at a homepoint for several minutes. It will
// be woken up again by the acceleromters or by plugging / unplugging power.
Log.i("VOUSSOIR", "TrackerService.state_sleep")
tracking_state = Keys.STATE_SLEEP
PreferencesHelper.saveTrackingState(tracking_state)
reset_location_listeners(Keys.LOCATION_INTERVAL_SLEEP)
arrived_at_home = arrived_at_home
gave_up_at = 0
stop_wakelock()
displayNotification()
}
fun state_dead()
{
// 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 /
// unplugging power.
Log.i("VOUSSOIR", "TrackerService.state_dead")
tracking_state = Keys.STATE_DEAD
PreferencesHelper.saveTrackingState(tracking_state)
reset_location_listeners(Keys.LOCATION_INTERVAL_STOP)
trackbook.database.commit()
recent_displacement_locations.clear()
arrived_at_home = 0
gave_up_at = System.currentTimeMillis()
stop_wakelock()
displayNotification()
}
fun state_mapview()
{
// This state should be activated when the user has the app open to the mapfragment, but is
// not recording. If the user closes the app while in this state, we can go to stop.
Log.i("VOUSSOIR", "TrackerService.state_mapview")
tracking_state = Keys.STATE_MAPVIEW
PreferencesHelper.saveTrackingState(tracking_state)
reset_location_listeners(Keys.LOCATION_INTERVAL_FULL_POWER)
arrived_at_home = 0
gave_up_at = 0
stop_wakelock()
displayNotification()
if (!gpsLocationListenerRegistered && !networkLocationListenerRegistered)
{
state_dead()
}
}
fun start_wakelock()
{
if (!wakelock.isHeld)
{
wakelock.acquire()
}
}
fun stop_wakelock()
{
if (wakelock.isHeld)
{
wakelock.release()
}
}
fun reset_location_listeners(interval: Long) fun reset_location_listeners(interval: Long)
{ {
Log.i("VOUSSOIR", "TrackerService.reset_location_listeners") Log.i("VOUSSOIR", "TrackerService.reset_location_listeners")
location_interval = interval if (use_gps_location && interval != Keys.LOCATION_INTERVAL_STOP)
if (use_gps_location && interval != Keys.LOCATION_INTERVAL_DEAD && interval != Keys.LOCATION_INTERVAL_STOP)
{ {
addGpsLocationListener(interval) add_gps_location_listener(interval)
} }
else if (gpsLocationListenerRegistered) else if (gpsLocationListenerRegistered)
{ {
removeGpsLocationListener() remove_gps_location_listener()
} }
if (use_network_location && interval != Keys.LOCATION_INTERVAL_DEAD && interval != Keys.LOCATION_INTERVAL_STOP) if (use_network_location && interval != Keys.LOCATION_INTERVAL_STOP)
{ {
addNetworkLocationListener(interval) add_network_location_listener(interval)
} }
else if (networkLocationListenerRegistered) else if (networkLocationListenerRegistered)
{ {
removeNetworkLocationListener() remove_network_location_listener()
}
if (interval != Keys.LOCATION_INTERVAL_DEAD)
{
gave_up_at = 0
} }
if (gpsLocationListenerRegistered || networkLocationListenerRegistered) if (gpsLocationListenerRegistered || networkLocationListenerRegistered)
{ {
listeners_enabled_at = System.currentTimeMillis() listeners_enabled_at = System.currentTimeMillis()
if (interval != Keys.LOCATION_INTERVAL_SLEEP)
{
arrived_at_home = 0
}
}
else if (interval == Keys.LOCATION_INTERVAL_STOP)
{
listeners_enabled_at = 0
} }
else else
{ {
listeners_enabled_at = 0 listeners_enabled_at = 0
location_interval = Keys.LOCATION_INTERVAL_DEAD
} }
val should_wakelock = (
(gpsLocationListenerRegistered || networkLocationListenerRegistered) &&
trackingState == Keys.STATE_TRACKING_ACTIVE &&
interval == Keys.LOCATION_INTERVAL_FULL_POWER
)
if (should_wakelock)
{
if (! wakelock.isHeld)
{
wakelock.acquire()
}
}
else if (wakelock.isHeld)
{
wakelock.release()
}
displayNotification() displayNotification()
} }
@ -253,10 +357,11 @@ class TrackerService: Service()
currentBestLocation = location currentBestLocation = location
if (trackingState != Keys.STATE_TRACKING_ACTIVE) if (tracking_state == Keys.STATE_STOP || tracking_state == Keys.STATE_MAPVIEW)
{ {
return return
} }
if(! trackbook.database.ready) if(! trackbook.database.ready)
{ {
Log.i("VOUSSOIR", "Omitting due to database not ready.") Log.i("VOUSSOIR", "Omitting due to database not ready.")
@ -283,34 +388,43 @@ class TrackerService: Service()
continue continue
} }
Log.i("VOUSSOIR", "Omitting due to homepoint ${index} ${homepoint.name}.") Log.i("VOUSSOIR", "Omitting due to homepoint ${index} ${homepoint.name}.")
// Move this homepoint to the front of the list so that on subsequent location
// updates it hits on the first loop. I'm sure this is a trivial amount of
// savings but oh well.
if (index > 0) if (index > 0)
{ {
trackbook.homepoints.remove(homepoint) trackbook.homepoints.remove(homepoint)
trackbook.homepoints.addFirst(homepoint) trackbook.homepoints.addFirst(homepoint)
} }
if (arrived_at_home == 0L) if (tracking_state != Keys.STATE_ARRIVED_AT_HOME && tracking_state != Keys.STATE_SLEEP)
{ {
Log.i("VOUSSOIR", "Arrived at home.") Log.i("VOUSSOIR", "Arrived at home.")
arrived_at_home = System.currentTimeMillis() state_arrived_at_home()
trackbook.database.commit()
} }
else if ( else if (
allow_sleep && allow_sleep &&
has_motion_sensor && has_motion_sensor &&
location_interval != Keys.LOCATION_INTERVAL_SLEEP && tracking_state == Keys.STATE_ARRIVED_AT_HOME &&
(System.currentTimeMillis() - arrived_at_home) > TIME_UNTIL_SLEEP && (System.currentTimeMillis() - arrived_at_home) > TIME_UNTIL_SLEEP &&
(System.currentTimeMillis() - last_significant_motion) > TIME_UNTIL_SLEEP (System.currentTimeMillis() - last_significant_motion) > TIME_UNTIL_SLEEP
) )
{ {
Log.i("VOUSSOIR", "Staying at home, sleeping.") Log.i("VOUSSOIR", "Staying at home, sleeping.")
reset_location_listeners(interval=Keys.LOCATION_INTERVAL_SLEEP) state_sleep()
} }
return return
} }
if (arrived_at_home > 0)
// All of the homepoint checks failed so we have left home and it's time to wake up.
// In practice we expect that the accelerometers would have triggered this change
// already, but this acts as a backup in case you somehow leave home without too
// much movement (maybe you sat in the car for several minutes before finally
// driving away or something).
if (tracking_state == Keys.STATE_ARRIVED_AT_HOME || tracking_state == Keys.STATE_SLEEP)
{ {
Log.i("VOUSSOIR", "Leaving home.") Log.i("VOUSSOIR", "Leaving home.")
reset_location_listeners(interval=Keys.LOCATION_INTERVAL_FULL_POWER) state_full_recording()
} }
if (! isRecentEnough(location)) if (! isRecentEnough(location))
@ -351,10 +465,10 @@ class TrackerService: Service()
recent_displacement_locations.removeFirst() recent_displacement_locations.removeFirst()
} }
if (location.time - lastCommit > Keys.COMMIT_INTERVAL) if ((location.time - last_commit) > Keys.COMMIT_INTERVAL)
{ {
trackbook.database.commit() trackbook.database.commit()
lastCommit = location.time last_commit = location.time
} }
} }
override fun onProviderEnabled(provider: String) override fun onProviderEnabled(provider: String)
@ -385,43 +499,34 @@ class TrackerService: Service()
} }
val timestamp = iso8601_local_noms(currentBestLocation.time) val timestamp = iso8601_local_noms(currentBestLocation.time)
if (trackingState == Keys.STATE_TRACKING_ACTIVE) if (tracking_state == Keys.STATE_FULL_RECORDING)
{
notification_builder.setContentTitle(this.getString(R.string.notification_title_trackbook_running))
if (location_interval == Keys.LOCATION_INTERVAL_FULL_POWER && arrived_at_home > 0)
{
notification_builder.setContentTitle("${timestamp} (home)")
notification_builder.setSmallIcon(R.drawable.ic_satellite_24dp)
}
else if (location_interval == Keys.LOCATION_INTERVAL_FULL_POWER)
{ {
notification_builder.setContentTitle("${timestamp} (recording)") notification_builder.setContentTitle("${timestamp} (recording)")
notification_builder.setSmallIcon(R.drawable.ic_satellite_24dp) notification_builder.setSmallIcon(R.drawable.ic_satellite_24dp)
} }
else if (location_interval == Keys.LOCATION_INTERVAL_SLEEP) else if (tracking_state == Keys.STATE_ARRIVED_AT_HOME)
{
notification_builder.setContentTitle("${timestamp} (home)")
notification_builder.setSmallIcon(R.drawable.ic_homepoint_24dp)
}
else if (tracking_state == Keys.STATE_SLEEP)
{ {
notification_builder.setContentTitle("${timestamp} (sleeping)") notification_builder.setContentTitle("${timestamp} (sleeping)")
notification_builder.setSmallIcon(R.drawable.ic_sleep_24dp) notification_builder.setSmallIcon(R.drawable.ic_sleep_24dp)
} }
else if (location_interval == Keys.LOCATION_INTERVAL_DEAD) else if (tracking_state == Keys.STATE_DEAD)
{ {
notification_builder.setContentTitle("${timestamp} (dead)") notification_builder.setContentTitle("${timestamp} (dead)")
notification_builder.setSmallIcon(R.drawable.ic_skull_24dp) notification_builder.setSmallIcon(R.drawable.ic_skull_24dp)
} }
else else if (tracking_state == Keys.STATE_STOP || tracking_state == Keys.STATE_MAPVIEW)
{
notification_builder.setContentText(timestamp)
notification_builder.setSmallIcon(R.drawable.ic_fiber_manual_record_inactive_24dp)
}
}
else
{ {
notification_builder.setContentTitle("${timestamp} (stopped)") notification_builder.setContentTitle("${timestamp} (stopped)")
notification_builder.setSmallIcon(R.drawable.ic_fiber_manual_stop_24dp) notification_builder.setSmallIcon(R.drawable.ic_fiber_manual_stop_24dp)
} }
val notification = notification_builder.build() val notification = notification_builder.build()
notificationManager.notify(Keys.TRACKER_SERVICE_NOTIFICATION_ID, notification) notification_manager.notify(Keys.TRACKER_SERVICE_NOTIFICATION_ID, notification)
return notification return notification
} }
@ -430,7 +535,7 @@ class TrackerService: Service()
/* Checks if notification channel exists */ /* Checks if notification channel exists */
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
private fun nowPlayingChannelExists() = notificationManager.getNotificationChannel(Keys.NOTIFICATION_CHANNEL_RECORDING) != null private fun nowPlayingChannelExists() = notification_manager.getNotificationChannel(Keys.NOTIFICATION_CHANNEL_RECORDING) != null
/* Create a notification channel */ /* Create a notification channel */
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
@ -441,18 +546,21 @@ class TrackerService: Service()
this.getString(R.string.notification_channel_recording_name), this.getString(R.string.notification_channel_recording_name),
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
).apply { description = this@TrackerService.getString(R.string.notification_channel_recording_description) } ).apply { description = this@TrackerService.getString(R.string.notification_channel_recording_description) }
notificationManager.createNotificationChannel(notificationChannel) notification_manager.createNotificationChannel(notificationChannel)
} }
/* Overrides onBind from Service */ /* Overrides onBind from Service */
override fun onBind(p0: Intent?): IBinder override fun onBind(p0: Intent?): IBinder
{ {
Log.i("VOUSSOIR", "TrackerService.onBind") Log.i("VOUSSOIR", "TrackerService.onBind")
if (listeners_enabled_at == 0L && location_interval != Keys.LOCATION_INTERVAL_DEAD) if (tracking_state == Keys.STATE_STOP)
{ {
reset_location_listeners(interval=Keys.LOCATION_INTERVAL_FULL_POWER) state_mapview()
} }
else
{
displayNotification() displayNotification()
}
bound = true bound = true
return binder return binder
} }
@ -461,11 +569,14 @@ class TrackerService: Service()
override fun onRebind(intent: Intent?) override fun onRebind(intent: Intent?)
{ {
Log.i("VOUSSOIR", "TrackerService.onRebind") Log.i("VOUSSOIR", "TrackerService.onRebind")
if (listeners_enabled_at == 0L && location_interval != Keys.LOCATION_INTERVAL_DEAD) if (tracking_state == Keys.STATE_STOP)
{ {
reset_location_listeners(interval=Keys.LOCATION_INTERVAL_FULL_POWER) state_mapview()
} }
else
{
displayNotification() displayNotification()
}
bound = true bound = true
} }
@ -475,10 +586,11 @@ class TrackerService: Service()
super.onUnbind(intent) super.onUnbind(intent)
Log.i("VOUSSOIR", "TrackerService.onUnbind") Log.i("VOUSSOIR", "TrackerService.onUnbind")
bound = false bound = false
// stop receiving location updates - if not tracking
if (trackingState != Keys.STATE_TRACKING_ACTIVE) // the user was only perusing the map and did not start recording, so we'll just stop.
if (tracking_state == Keys.STATE_MAPVIEW)
{ {
reset_location_listeners(interval=Keys.LOCATION_INTERVAL_STOP) state_stop()
} }
// ensures onRebind is called // ensures onRebind is called
return true return true
@ -498,9 +610,10 @@ class TrackerService: Service()
device_id = PreferencesHelper.load_device_id() device_id = PreferencesHelper.load_device_id()
useImperial = PreferencesHelper.loadUseImperialUnits() useImperial = PreferencesHelper.loadUseImperialUnits()
omitRests = PreferencesHelper.loadOmitRests() omitRests = PreferencesHelper.loadOmitRests()
max_accuracy = PreferencesHelper.load_max_accuracy()
allow_sleep = PreferencesHelper.loadAllowSleep() allow_sleep = PreferencesHelper.loadAllowSleep()
locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notification_manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notification_builder = NotificationCompat.Builder(this, Keys.NOTIFICATION_CHANNEL_RECORDING) notification_builder = NotificationCompat.Builder(this, Keys.NOTIFICATION_CHANNEL_RECORDING)
val showActionPendingIntent: PendingIntent? = TaskStackBuilder.create(this).run { val showActionPendingIntent: PendingIntent? = TaskStackBuilder.create(this).run {
addNextIntentWithParentStack(Intent(this@TrackerService, MainActivity::class.java)) addNextIntentWithParentStack(Intent(this@TrackerService, MainActivity::class.java))
@ -512,7 +625,6 @@ class TrackerService: Service()
networkProviderActive = isNetworkEnabled(locationManager) networkProviderActive = isNetworkEnabled(locationManager)
gpsLocationListener = createLocationListener() gpsLocationListener = createLocationListener()
networkLocationListener = createLocationListener() networkLocationListener = createLocationListener()
trackingState = PreferencesHelper.loadTrackingState()
currentBestLocation = getLastKnownLocation(this) currentBestLocation = getLastKnownLocation(this)
PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener) PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener)
@ -526,10 +638,10 @@ class TrackerService: Service()
override fun onTrigger(event: TriggerEvent?) { override fun onTrigger(event: TriggerEvent?) {
Log.i("VOUSSOIR", "Significant motion") Log.i("VOUSSOIR", "Significant motion")
last_significant_motion = System.currentTimeMillis() last_significant_motion = System.currentTimeMillis()
if (trackingState == Keys.STATE_TRACKING_ACTIVE && location_interval != Keys.LOCATION_INTERVAL_FULL_POWER) if (tracking_state == Keys.STATE_SLEEP || tracking_state == Keys.STATE_DEAD)
{ {
vibrator.vibrate(100) vibrator.vibrate(100)
reset_location_listeners(interval=Keys.LOCATION_INTERVAL_FULL_POWER) state_full_recording()
} }
sensor_manager.requestTriggerSensor(this, significant_motion_sensor) sensor_manager.requestTriggerSensor(this, significant_motion_sensor)
} }
@ -546,11 +658,10 @@ class TrackerService: Service()
override fun onSensorChanged(event: SensorEvent?) { override fun onSensorChanged(event: SensorEvent?) {
Log.i("VOUSSOIR", "Step counter changed") Log.i("VOUSSOIR", "Step counter changed")
last_significant_motion = System.currentTimeMillis() last_significant_motion = System.currentTimeMillis()
if (trackingState == Keys.STATE_TRACKING_ACTIVE && location_interval != Keys.LOCATION_INTERVAL_FULL_POWER) if (tracking_state == Keys.STATE_SLEEP || tracking_state == Keys.STATE_DEAD)
{ {
// beeper.startTone(ToneGenerator.TONE_PROP_ACK, 150)
vibrator.vibrate(100) vibrator.vibrate(100)
reset_location_listeners(interval=Keys.LOCATION_INTERVAL_FULL_POWER) state_full_recording()
} }
} }
@ -574,9 +685,9 @@ class TrackerService: Service()
{ {
device_is_charging = false device_is_charging = false
} }
if (trackingState == Keys.STATE_TRACKING_ACTIVE) if (tracking_state == Keys.STATE_SLEEP || tracking_state == Keys.STATE_DEAD)
{ {
reset_location_listeners(interval=Keys.LOCATION_INTERVAL_FULL_POWER) state_full_recording()
} }
} }
} }
@ -588,6 +699,7 @@ class TrackerService: Service()
val powermanager = getSystemService(Context.POWER_SERVICE) as PowerManager val powermanager = getSystemService(Context.POWER_SERVICE) as PowerManager
wakelock = powermanager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "trkpt::wakelock") wakelock = powermanager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "trkpt::wakelock")
load_tracking_state()
handler.post(background_watchdog) handler.post(background_watchdog)
} }
@ -599,19 +711,19 @@ class TrackerService: Service()
// SERVICE RESTART (via START_STICKY) // SERVICE RESTART (via START_STICKY)
if (intent == null) if (intent == null)
{ {
if (trackingState == Keys.STATE_TRACKING_ACTIVE) if (tracking_state != Keys.STATE_STOP && tracking_state != Keys.STATE_MAPVIEW)
{ {
Log.w("VOUSSOIR", "Trackbook has been killed by the operating system. Trying to resume recording.") Log.w("VOUSSOIR", "Trackbook has been killed by the operating system. Trying to resume recording.")
startTracking() state_full_recording()
} }
} }
else if (intent.action == Keys.ACTION_STOP) else if (intent.action == Keys.ACTION_STOP)
{ {
stopTracking() state_stop()
} }
else if (intent.action == Keys.ACTION_START) else if (intent.action == Keys.ACTION_START)
{ {
startTracking() state_full_recording()
} }
// START_STICKY is used for services that are explicitly started and stopped as needed // START_STICKY is used for services that are explicitly started and stopped as needed
@ -623,51 +735,24 @@ class TrackerService: Service()
{ {
Log.i("VOUSSOIR", "TrackerService.onDestroy.") Log.i("VOUSSOIR", "TrackerService.onDestroy.")
super.onDestroy() super.onDestroy()
if (trackingState == Keys.STATE_TRACKING_ACTIVE) state_stop()
{
stopTracking()
}
stopForeground(STOP_FOREGROUND_REMOVE)
PreferencesHelper.unregisterPreferenceChangeListener(sharedPreferenceChangeListener) PreferencesHelper.unregisterPreferenceChangeListener(sharedPreferenceChangeListener)
reset_location_listeners(interval=Keys.LOCATION_INTERVAL_DEAD)
handler.removeCallbacks(background_watchdog) handler.removeCallbacks(background_watchdog)
unregisterReceiver(charging_broadcast_receiver) unregisterReceiver(charging_broadcast_receiver)
} }
fun startTracking()
{
Log.i("VOUSSOIR", "TrackerService.startTracking")
trackingState = Keys.STATE_TRACKING_ACTIVE
reset_location_listeners(interval=Keys.LOCATION_INTERVAL_FULL_POWER)
PreferencesHelper.saveTrackingState(trackingState)
recent_displacement_locations.clear()
startForeground(Keys.TRACKER_SERVICE_NOTIFICATION_ID, displayNotification())
}
fun stopTracking()
{
Log.i("VOUSSOIR", "TrackerService.stopTracking")
trackbook.database.commit()
trackingState = Keys.STATE_TRACKING_STOPPED
reset_location_listeners(interval=Keys.LOCATION_INTERVAL_FULL_POWER)
PreferencesHelper.saveTrackingState(trackingState)
recent_displacement_locations.clear()
displayNotification()
stopForeground(STOP_FOREGROUND_DETACH)
}
private val sharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key -> private val sharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
when (key) when (key)
{ {
Keys.PREF_LOCATION_GPS -> Keys.PREF_LOCATION_GPS ->
{ {
use_gps_location = PreferencesHelper.load_location_gps() use_gps_location = PreferencesHelper.load_location_gps()
reset_location_listeners(interval=Keys.LOCATION_INTERVAL_FULL_POWER) state_full_recording()
} }
Keys.PREF_LOCATION_NETWORK -> Keys.PREF_LOCATION_NETWORK ->
{ {
use_network_location = PreferencesHelper.load_location_network() use_network_location = PreferencesHelper.load_location_network()
reset_location_listeners(interval=Keys.LOCATION_INTERVAL_FULL_POWER) state_full_recording()
} }
Keys.PREF_USE_IMPERIAL_UNITS -> Keys.PREF_USE_IMPERIAL_UNITS ->
{ {
@ -684,9 +769,9 @@ class TrackerService: Service()
Keys.PREF_ALLOW_SLEEP -> Keys.PREF_ALLOW_SLEEP ->
{ {
allow_sleep = PreferencesHelper.loadAllowSleep() allow_sleep = PreferencesHelper.loadAllowSleep()
if (! allow_sleep && trackingState == Keys.STATE_TRACKING_ACTIVE && location_interval != Keys.LOCATION_INTERVAL_FULL_POWER) if (! allow_sleep && (tracking_state == Keys.STATE_SLEEP || tracking_state == Keys.STATE_DEAD))
{ {
reset_location_listeners(interval=Keys.LOCATION_INTERVAL_FULL_POWER) state_full_recording()
} }
} }
Keys.PREF_DEVICE_ID -> Keys.PREF_DEVICE_ID ->
@ -723,21 +808,19 @@ class TrackerService: Service()
has_motion_sensor && has_motion_sensor &&
!device_is_charging && !device_is_charging &&
// We only go to dead during active tracking because if you are looking at the // We only go to dead during active tracking because if you are looking at the
// device in non-tracking mode you are probably waiting for your signal to recover. // device in mapview state you are probably waiting for your signal to recover.
trackingState == Keys.STATE_TRACKING_ACTIVE &&
// We only go to dead from full power because in the sleep state, the wakelock is // We only go to dead from full power because in the sleep state, the wakelock is
// turned off and the device may go into doze. During doze, the device stops // turned off and the device may go into doze. During doze, the device stops
// updating the location listeners anyway, so there is no benefit in going to dead. // updating the location listeners anyway, so there is no benefit in going to dead.
// When the user interacts with the device and it leaves doze, it's better to come // When the user interacts with the device and it leaves doze, it's better to come
// from sleep state than dead state. // from sleep state than dead state.
location_interval == Keys.LOCATION_INTERVAL_FULL_POWER && tracking_state == Keys.STATE_FULL_RECORDING &&
(now - listeners_enabled_at) > TIME_UNTIL_DEAD && (now - listeners_enabled_at) > TIME_UNTIL_DEAD &&
(now - currentBestLocation.time) > TIME_UNTIL_DEAD && (now - currentBestLocation.time) > TIME_UNTIL_DEAD &&
(now - last_significant_motion) > TIME_UNTIL_DEAD (now - last_significant_motion) > TIME_UNTIL_DEAD
) )
{ {
reset_location_listeners(interval=Keys.LOCATION_INTERVAL_DEAD) state_dead()
gave_up_at = now
} }
} }
} }

View File

@ -1,142 +0,0 @@
/*
* TrackingToggleTileService.kt
* Implements the TrackingToggleTileService service
* A TrackingToggleTileService toggles the recording state from a quick settings tile
*
* This file is part of
* TRACKBOOK - Movement Recorder for Android
*
* Copyright (c) 2016-22 - Y20K.org
* Licensed under the MIT-License
* http://opensource.org/licenses/MIT
*
* Trackbook uses osmdroid - OpenStreetMap-Tools for Android
* https://github.com/osmdroid/osmdroid
*/
/*
* Modified by voussoir for trkpt, forked from Trackbook.
*/
package net.voussoir.trkpt
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.drawable.Icon
import android.os.Build
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import net.voussoir.trkpt.helpers.PreferencesHelper
/*
* TrackingToggleTileService class
*/
class TrackingToggleTileService: TileService()
{
/* Main class variables */
private var bound: Boolean = false
private var trackingState: Int = Keys.STATE_TRACKING_STOPPED
private lateinit var trackerService: TrackerService
/* Overrides onTileAdded from TileService */
override fun onTileAdded() {
super.onTileAdded()
// get saved tracking state
trackingState = PreferencesHelper.loadTrackingState()
// set up tile
updateTile()
}
/* Overrides onTileRemoved from TileService */
override fun onTileRemoved() {
super.onTileRemoved()
}
/* Overrides onStartListening from TileService (tile becomes visible) */
override fun onStartListening() {
super.onStartListening()
// get saved tracking state
trackingState = PreferencesHelper.loadTrackingState()
// set up tile
updateTile()
// register listener for changes in shared preferences
PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener)
}
/* Overrides onClick from TileService */
override fun onClick() {
super.onClick()
when (trackingState) {
Keys.STATE_TRACKING_ACTIVE -> stopTracking()
else -> startTracking()
}
}
/* Overrides onStopListening from TileService (tile no longer visible) */
override fun onStopListening() {
super.onStopListening()
// unregister listener for changes in shared preferences
PreferencesHelper.unregisterPreferenceChangeListener(sharedPreferenceChangeListener)
}
/* Overrides onDestroy from Service */
override fun onDestroy() {
super.onDestroy()
}
/* Update quick settings tile */
private fun updateTile() {
val tile: Tile = qsTile
tile.icon = Icon.createWithResource(this, R.drawable.ic_notification_icon_small_24dp)
when (trackingState) {
Keys.STATE_TRACKING_ACTIVE -> {
tile.label = getString(R.string.quick_settings_tile_title_pause)
tile.contentDescription = getString(R.string.descr_quick_settings_tile_title_pause)
tile.state = Tile.STATE_ACTIVE
}
else -> {
tile.label = getString(R.string.quick_settings_tile_title_start)
tile.contentDescription = getString(R.string.descr_quick_settings_tile_title_start)
tile.state = Tile.STATE_INACTIVE
}
}
tile.updateTile()
}
/* Start tracking */
private fun startTracking() {
val intent = Intent(application, TrackerService::class.java)
intent.action = Keys.ACTION_START
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// ... start service in foreground to prevent it being killed on Oreo
application.startForegroundService(intent)
} else {
application.startService(intent)
}
}
/* Stop tracking */
private fun stopTracking() {
val intent = Intent(application, TrackerService::class.java)
intent.action = Keys.ACTION_STOP
application.startService(intent)
}
/*
* Defines the listener for changes in shared preferences
*/
private val sharedPreferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
when (key) {
Keys.PREF_TRACKING_STATE -> {
trackingState = PreferencesHelper.loadTrackingState()
updateTile()
}
}
}
/*
* End of declaration
*/
}

View File

@ -75,7 +75,7 @@ object PreferencesHelper
} }
fun loadTrackingState(): Int { fun loadTrackingState(): Int {
return sharedPreferences.getInt(Keys.PREF_TRACKING_STATE, Keys.STATE_TRACKING_STOPPED) return sharedPreferences.getInt(Keys.PREF_TRACKING_STATE, Keys.STATE_STOP)
} }
fun saveTrackingState(trackingState: Int) { fun saveTrackingState(trackingState: Int) {

View File

@ -30,6 +30,7 @@ import net.voussoir.trkpt.Keys
import net.voussoir.trkpt.R import net.voussoir.trkpt.R
import net.voussoir.trkpt.Database import net.voussoir.trkpt.Database
import net.voussoir.trkpt.Track import net.voussoir.trkpt.Track
import net.voussoir.trkpt.helpers.PreferencesHelper
import java.text.DateFormat import java.text.DateFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -53,9 +54,10 @@ class TracklistAdapter(val fragment: Fragment, val database: Database) : Recycle
{ {
return return
} }
val max_accuracy = PreferencesHelper.load_max_accuracy()
val cursor: Cursor = database.connection.rawQuery( val cursor: Cursor = database.connection.rawQuery(
"SELECT distinct(date(time/1000, 'unixepoch', 'localtime')) as thedate, device_id FROM trkpt ORDER BY thedate DESC", "SELECT distinct(date(time/1000, 'unixepoch', 'localtime')) as thedate, device_id FROM trkpt WHERE accuracy <= ? ORDER BY thedate DESC",
arrayOf() arrayOf(max_accuracy.toString())
) )
try try
{ {

View File

@ -91,7 +91,7 @@
<string name="pref_advanced_title">Advanced</string> <string name="pref_advanced_title">Advanced</string>
<string name="pref_delete_non_starred_summary">Delete all recordings in \"Tracks\" that are not starred.</string> <string name="pref_delete_non_starred_summary">Delete all recordings in \"Tracks\" that are not starred.</string>
<string name="pref_delete_non_starred_title">Delete Non-Starred Recordings</string> <string name="pref_delete_non_starred_title">Delete Non-Starred Recordings</string>
<string name="pref_general_title">General</string> <string name="pref_general_title">Settings</string>
<string name="pref_maintenance_title">Maintenance</string> <string name="pref_maintenance_title">Maintenance</string>
<string name="pref_location_gps_title">Use GPS location</string> <string name="pref_location_gps_title">Use GPS location</string>
<string name="pref_location_gps_summary_on">GPS location enabled.</string> <string name="pref_location_gps_summary_on">GPS location enabled.</string>