Rewrite TrackerService to be more like a state machine.
parent
2e5be6a4e4
commit
3710755bc1
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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 reset_location_listeners(interval: Long)
|
fun load_tracking_state()
|
||||||
{
|
{
|
||||||
Log.i("VOUSSOIR", "TrackerService.reset_location_listeners")
|
tracking_state = PreferencesHelper.loadTrackingState()
|
||||||
location_interval = interval
|
when (tracking_state)
|
||||||
if (use_gps_location && interval != Keys.LOCATION_INTERVAL_DEAD && interval != Keys.LOCATION_INTERVAL_STOP)
|
|
||||||
{
|
{
|
||||||
addGpsLocationListener(interval)
|
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()
|
||||||
}
|
}
|
||||||
else if (gpsLocationListenerRegistered)
|
|
||||||
{
|
|
||||||
removeGpsLocationListener()
|
|
||||||
}
|
|
||||||
if (use_network_location && interval != Keys.LOCATION_INTERVAL_DEAD && interval != Keys.LOCATION_INTERVAL_STOP)
|
|
||||||
{
|
|
||||||
addNetworkLocationListener(interval)
|
|
||||||
}
|
|
||||||
else if (networkLocationListenerRegistered)
|
|
||||||
{
|
|
||||||
removeNetworkLocationListener()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (interval != Keys.LOCATION_INTERVAL_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
|
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)
|
if (gpsLocationListenerRegistered || networkLocationListenerRegistered)
|
||||||
{
|
{
|
||||||
listeners_enabled_at = System.currentTimeMillis()
|
start_wakelock()
|
||||||
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
|
state_dead()
|
||||||
location_interval = Keys.LOCATION_INTERVAL_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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val should_wakelock = (
|
fun start_wakelock()
|
||||||
(gpsLocationListenerRegistered || networkLocationListenerRegistered) &&
|
|
||||||
trackingState == Keys.STATE_TRACKING_ACTIVE &&
|
|
||||||
interval == Keys.LOCATION_INTERVAL_FULL_POWER
|
|
||||||
)
|
|
||||||
if (should_wakelock)
|
|
||||||
{
|
{
|
||||||
if (!wakelock.isHeld)
|
if (!wakelock.isHeld)
|
||||||
{
|
{
|
||||||
wakelock.acquire()
|
wakelock.acquire()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (wakelock.isHeld)
|
|
||||||
|
fun stop_wakelock()
|
||||||
|
{
|
||||||
|
if (wakelock.isHeld)
|
||||||
{
|
{
|
||||||
wakelock.release()
|
wakelock.release()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reset_location_listeners(interval: Long)
|
||||||
|
{
|
||||||
|
Log.i("VOUSSOIR", "TrackerService.reset_location_listeners")
|
||||||
|
if (use_gps_location && interval != Keys.LOCATION_INTERVAL_STOP)
|
||||||
|
{
|
||||||
|
add_gps_location_listener(interval)
|
||||||
|
}
|
||||||
|
else if (gpsLocationListenerRegistered)
|
||||||
|
{
|
||||||
|
remove_gps_location_listener()
|
||||||
|
}
|
||||||
|
if (use_network_location && interval != Keys.LOCATION_INTERVAL_STOP)
|
||||||
|
{
|
||||||
|
add_network_location_listener(interval)
|
||||||
|
}
|
||||||
|
else if (networkLocationListenerRegistered)
|
||||||
|
{
|
||||||
|
remove_network_location_listener()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gpsLocationListenerRegistered || networkLocationListenerRegistered)
|
||||||
|
{
|
||||||
|
listeners_enabled_at = System.currentTimeMillis()
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
listeners_enabled_at = 0
|
||||||
|
}
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue