Also watch step counter movement, notification icon by status.

master
voussoir 2023-04-01 13:56:27 -07:00
parent 59389accc5
commit 6ad4c2dbe5
7 changed files with 178 additions and 154 deletions

View File

@ -5,6 +5,7 @@
<!-- USE GPS AND NETWORK - EXCLUDE NON-GPS DEVICES --> <!-- USE GPS AND NETWORK - EXCLUDE NON-GPS DEVICES -->
<uses-feature android:name="android.hardware.location.gps" android:required="true" /> <uses-feature android:name="android.hardware.location.gps" android:required="true" />
<uses-feature android:name="android.hardware.location.network" /> <uses-feature android:name="android.hardware.location.network" />
<uses-feature android:name="android.hardware.sensor.stepcounter" />
<!-- NORMAL PERMISSIONS, automatically granted --> <!-- NORMAL PERMISSIONS, automatically granted -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
@ -15,6 +16,7 @@
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<!-- DANGEROUS PERMISSIONS, must request --> <!-- DANGEROUS PERMISSIONS, must request -->
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

View File

@ -26,6 +26,11 @@ object Keys {
// application name // application name
const val APPLICATION_NAME: String = "trkpt" const val APPLICATION_NAME: String = "trkpt"
// axioms
const val ONE_SECOND_IN_MILLISECONDS: Long = 1000
const val ONE_MINUTE_IN_MILLISECONDS: Long = 60 * ONE_SECOND_IN_MILLISECONDS
const val ONE_HOUR_IN_MILLISECONDS: Long = 60 * ONE_MINUTE_IN_MILLISECONDS
// version numbers // version numbers
const val CURRENT_TRACK_FORMAT_VERSION: Int = 4 const val CURRENT_TRACK_FORMAT_VERSION: Int = 4
const val DATABASE_VERSION: Int = 1 const val DATABASE_VERSION: Int = 1
@ -63,6 +68,9 @@ object Keys {
// states // states
const val STATE_TRACKING_STOPPED: Int = 0 const val STATE_TRACKING_STOPPED: Int = 0
const val STATE_TRACKING_ACTIVE: Int = 1 const val STATE_TRACKING_ACTIVE: Int = 1
const val LOCATION_INTERVAL_FULL_POWER: Long = 0
const val LOCATION_INTERVAL_SLEEP: Long = ONE_MINUTE_IN_MILLISECONDS
const val LOCATION_INTERVAL_GIVE_UP: Long = -1
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"
@ -85,9 +93,6 @@ object Keys {
// default values // default values
val DEFAULT_DATE: Date = Date(0L) val DEFAULT_DATE: Date = Date(0L)
const val ONE_SECOND_IN_MILLISECONDS: Long = 1000
const val ONE_MINUTE_IN_MILLISECONDS: Long = 60 * ONE_SECOND_IN_MILLISECONDS
const val ONE_HOUR_IN_MILLISECONDS: Long = 60 * ONE_MINUTE_IN_MILLISECONDS
const val EMPTY_STRING_RESOURCE: Int = 0 const val EMPTY_STRING_RESOURCE: Int = 0
const val REQUEST_CURRENT_LOCATION_INTERVAL: Long = 1 * ONE_SECOND_IN_MILLISECONDS const val REQUEST_CURRENT_LOCATION_INTERVAL: Long = 1 * ONE_SECOND_IN_MILLISECONDS
const val SAVE_TEMP_TRACK_INTERVAL: Long = 30 * ONE_SECOND_IN_MILLISECONDS const val SAVE_TEMP_TRACK_INTERVAL: Long = 30 * ONE_SECOND_IN_MILLISECONDS

View File

@ -107,6 +107,7 @@ class MainActivity: AppCompatActivity()
Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACTIVITY_RECOGNITION,
) )
val permissions_needed = ArrayList<String>() val permissions_needed = ArrayList<String>()
for (permission in permissions_wanted) for (permission in permissions_wanted)

View File

@ -71,7 +71,6 @@ class MapFragment : Fragment()
lateinit var zoom_out_button: FloatingActionButton lateinit var zoom_out_button: FloatingActionButton
lateinit var currentLocationButton: FloatingActionButton lateinit var currentLocationButton: FloatingActionButton
lateinit var map_current_time: TextView lateinit var map_current_time: TextView
lateinit var power_level_indicator: ImageButton
private var current_track_overlay: Polyline? = null private var current_track_overlay: Polyline? = null
private var current_position_overlays = ArrayList<Overlay>() private var current_position_overlays = ArrayList<Overlay>()
private var homepoints_overlays = ArrayList<Overlay>() private var homepoints_overlays = ArrayList<Overlay>()
@ -113,7 +112,6 @@ class MapFragment : Fragment()
zoom_in_button = rootView.findViewById(R.id.zoom_in_button) zoom_in_button = rootView.findViewById(R.id.zoom_in_button)
zoom_out_button = rootView.findViewById(R.id.zoom_out_button) zoom_out_button = rootView.findViewById(R.id.zoom_out_button)
map_current_time = rootView.findViewById(R.id.map_current_time) map_current_time = rootView.findViewById(R.id.map_current_time)
power_level_indicator = rootView.findViewById(R.id.power_level_indicator)
mainButton = rootView.findViewById(R.id.main_button) mainButton = rootView.findViewById(R.id.main_button)
locationErrorBar = Snackbar.make(mapView, String(), Snackbar.LENGTH_INDEFINITE) locationErrorBar = Snackbar.make(mapView, String(), Snackbar.LENGTH_INDEFINITE)
@ -390,38 +388,74 @@ class MapFragment : Fragment()
} }
/* Mark current position on map */ /* Mark current position on map */
fun create_current_position_overlays(location: Location, trackingState: Int = Keys.STATE_TRACKING_STOPPED) fun create_current_position_overlays()
{ {
clear_current_position_overlays() clear_current_position_overlays()
val locationIsOld: Boolean = !(isRecentEnough(location)) val tracker = trackerService
if (tracker == null)
{
return
}
val locationIsOld: Boolean = !(isRecentEnough(tracker.currentBestLocation))
val newMarker: Drawable val newMarker: Drawable
val fillcolor: Int val fillcolor: Int
if (locationIsOld) val description: String
if (tracker.listeners_enabled_at == 0L)
{
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_GIVE_UP)
{
fillcolor = Color.argb(64, 0, 0, 0)
newMarker = ContextCompat.getDrawable(requireContext(), R.drawable.ic_skull_24dp)!!
description = "GPS is struggling; disabled until movement"
}
else if (tracker.trackingState == Keys.STATE_TRACKING_ACTIVE && tracker.location_interval == Keys.LOCATION_INTERVAL_SLEEP)
{
fillcolor = Color.argb(64, 220, 61, 51)
newMarker = ContextCompat.getDrawable(requireContext(), R.drawable.ic_sleep_24dp)!!
description = "GPS sleeping until movement"
}
else if (locationIsOld)
{ {
fillcolor = Color.argb(64, 0, 0, 0) fillcolor = Color.argb(64, 0, 0, 0)
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"
} }
else if (trackingState == Keys.STATE_TRACKING_ACTIVE) else if (tracker.trackingState == Keys.STATE_TRACKING_ACTIVE)
{ {
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)!!
description = "GPS tracking at full power"
} }
else else
{ {
fillcolor = Color.argb(64, 60, 152, 219) fillcolor = Color.argb(64, 60, 152, 219)
newMarker = ContextCompat.getDrawable(requireContext(), R.drawable.ic_marker_location_blue_24dp)!! newMarker = ContextCompat.getDrawable(requireContext(), R.drawable.ic_marker_location_blue_24dp)!!
description = "GPS tracking at full power"
} }
val current_location_radius = Polygon() val current_location_radius = Polygon()
current_location_radius.points = Polygon.pointsAsCircle(GeoPoint(location.latitude, location.longitude), location.accuracy.toDouble()) current_location_radius.points = Polygon.pointsAsCircle(
GeoPoint(tracker.currentBestLocation.latitude, tracker.currentBestLocation.longitude),
tracker.currentBestLocation.accuracy.toDouble()
)
current_location_radius.fillPaint.color = fillcolor current_location_radius.fillPaint.color = fillcolor
current_location_radius.outlinePaint.color = Color.argb(0, 0, 0, 0) current_location_radius.outlinePaint.color = Color.argb(0, 0, 0, 0)
current_position_overlays.add(current_location_radius) current_position_overlays.add(current_location_radius)
val overlayItems: java.util.ArrayList<OverlayItem> = java.util.ArrayList<OverlayItem>() val overlayItems: java.util.ArrayList<OverlayItem> = java.util.ArrayList<OverlayItem>()
val overlayItem: OverlayItem = createOverlayItem(requireContext(), location.latitude, location.longitude, location.accuracy, location.provider.toString(), location.time) val overlayItem: OverlayItem = createOverlayItem(
tracker.currentBestLocation.latitude,
tracker.currentBestLocation.longitude,
title="Current location",
description=description,
)
overlayItem.setMarker(newMarker) overlayItem.setMarker(newMarker)
overlayItems.add(overlayItem) overlayItems.add(overlayItem)
current_position_overlays.add(createOverlay(requireContext(), overlayItems)) current_position_overlays.add(createOverlay(requireContext(), overlayItems))
@ -478,12 +512,10 @@ class MapFragment : Fragment()
val overlayItems: java.util.ArrayList<OverlayItem> = java.util.ArrayList<OverlayItem>() val overlayItems: java.util.ArrayList<OverlayItem> = java.util.ArrayList<OverlayItem>()
val overlayItem: OverlayItem = createOverlayItem( val overlayItem: OverlayItem = createOverlayItem(
context,
homepoint.location.latitude, homepoint.location.latitude,
homepoint.location.longitude, homepoint.location.longitude,
homepoint.location.accuracy, title=homepoint.name,
homepoint.location.provider.toString(), description="Radius ${homepoint.radius}"
homepoint.location.time
) )
overlayItem.setMarker(newMarker) overlayItem.setMarker(newMarker)
overlayItems.add(overlayItem) overlayItems.add(overlayItem)
@ -615,7 +647,7 @@ class MapFragment : Fragment()
{ {
return return
} }
create_current_position_overlays(tracker.currentBestLocation, tracker.trackingState) create_current_position_overlays()
if (current_track_overlay == null) if (current_track_overlay == null)
{ {
create_track_overlay() create_track_overlay()
@ -629,28 +661,7 @@ class MapFragment : Fragment()
map_current_time.text = iso8601_local_noms(tracker.currentBestLocation.time) map_current_time.text = iso8601_local_noms(tracker.currentBestLocation.time)
mapView.invalidate()
if (tracker.location_interval == tracker.LOCATION_INTERVAL_FULL_POWER)
{
power_level_indicator.setImageResource(R.drawable.ic_satellite_24dp)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
power_level_indicator.tooltipText = "GPS tracking at full power"
}
}
else if (tracker.location_interval == tracker.LOCATION_INTERVAL_SLEEP)
{
power_level_indicator.setImageResource(R.drawable.ic_sleep_24dp)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
power_level_indicator.tooltipText = "GPS sleeping until movement"
}
}
else if (tracker.location_interval == tracker.LOCATION_INTERVAL_GIVE_UP)
{
power_level_indicator.setImageResource(R.drawable.ic_skull_24dp)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
power_level_indicator.tooltipText = "GPS is struggling; disabled until movement"
}
}
} }
val redraw_runnable: Runnable = object : Runnable val redraw_runnable: Runnable = object : Runnable

View File

@ -31,10 +31,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.hardware.Sensor import android.hardware.*
import android.hardware.SensorManager
import android.hardware.TriggerEvent
import android.hardware.TriggerEventListener
import android.location.Location import android.location.Location
import android.location.LocationListener import android.location.LocationListener
import android.location.LocationManager import android.location.LocationManager
@ -68,9 +65,9 @@ class TrackerService: Service()
var last_significant_motion: Long = 0 var last_significant_motion: Long = 0
var arrived_at_home: Long = 0 var arrived_at_home: Long = 0
var location_interval: Long = 0 var location_interval: Long = 0
val LOCATION_INTERVAL_FULL_POWER: Long = 0 val TIME_UNTIL_SLEEP: Long = 2 * Keys.ONE_MINUTE_IN_MILLISECONDS
val LOCATION_INTERVAL_SLEEP: Long = Keys.ONE_MINUTE_IN_MILLISECONDS val TIME_UNTIL_GIVE_UP: Long = 3 * Keys.ONE_MINUTE_IN_MILLISECONDS
val LOCATION_INTERVAL_GIVE_UP: Long = -1 val WATCHDOG_INTERVAL: Long = 30 * Keys.ONE_SECOND_IN_MILLISECONDS
private val RECENT_TRKPT_COUNT = 3600 private val RECENT_TRKPT_COUNT = 3600
private val DISPLACEMENT_LOCATION_COUNT = 5 private val DISPLACEMENT_LOCATION_COUNT = 5
lateinit var recent_displacement_locations: Deque<Location> lateinit var recent_displacement_locations: Deque<Location>
@ -94,27 +91,23 @@ class TrackerService: Service()
private lateinit var sensor_manager: SensorManager private lateinit var sensor_manager: SensorManager
private var significant_motion_sensor: Sensor? = null private var significant_motion_sensor: Sensor? = null
private var step_counter_sensor: Sensor? = null
var has_motion_sensor: Boolean = false
private fun addGpsLocationListener(interval: Long) private fun addGpsLocationListener(interval: Long): Boolean
{ {
if (! use_gps_location)
{
Log.i("VOUSSOIR", "Skipping GPS listener.")
return
}
gpsProviderActive = isGpsEnabled(locationManager) gpsProviderActive = isGpsEnabled(locationManager)
if (! gpsProviderActive) if (! gpsProviderActive)
{ {
Log.w("VOUSSOIR", "Device GPS is not enabled.") Log.w("VOUSSOIR", "Device GPS is not enabled.")
return return false
} }
val has_permission: Boolean = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED val has_permission: Boolean = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
if (! has_permission) if (! has_permission)
{ {
Log.w("VOUSSOIR", "Location permission is not granted.") Log.w("VOUSSOIR", "Location permission is not granted.")
return return false
} }
locationManager.requestLocationUpdates( locationManager.requestLocationUpdates(
@ -125,28 +118,23 @@ class TrackerService: Service()
) )
gpsLocationListenerRegistered = true gpsLocationListenerRegistered = true
Log.i("VOUSSOIR", "Added GPS location listener.") Log.i("VOUSSOIR", "Added GPS location listener.")
return true
} }
private fun addNetworkLocationListener(interval: Long) private fun addNetworkLocationListener(interval: Long): Boolean
{ {
if (! use_network_location)
{
Log.i("VOUSSOIR", "Skipping Network listener.")
return
}
networkProviderActive = isNetworkEnabled(locationManager) networkProviderActive = isNetworkEnabled(locationManager)
if (!networkProviderActive) if (!networkProviderActive)
{ {
Log.w("VOUSSOIR", "Unable to add Network location listener.") Log.w("VOUSSOIR", "Unable to add Network location listener.")
return return false
} }
val has_permission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED val has_permission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
if (! has_permission) if (! has_permission)
{ {
Log.w("VOUSSOIR", "Unable to add Network location listener. Location permission is not granted.") Log.w("VOUSSOIR", "Unable to add Network location listener. Location permission is not granted.")
return return false
} }
locationManager.requestLocationUpdates( locationManager.requestLocationUpdates(
@ -157,6 +145,7 @@ class TrackerService: Service()
) )
networkLocationListenerRegistered = true networkLocationListenerRegistered = true
Log.i("VOUSSOIR", "Added Network location listener.") Log.i("VOUSSOIR", "Added Network location listener.")
return true
} }
fun removeGpsLocationListener() fun removeGpsLocationListener()
@ -193,19 +182,17 @@ class TrackerService: Service()
location_interval = interval location_interval = interval
var gps_added = false var gps_added = false
var network_added = false var network_added = false
if (use_gps_location && interval != LOCATION_INTERVAL_GIVE_UP) if (use_gps_location && interval != Keys.LOCATION_INTERVAL_GIVE_UP)
{ {
addGpsLocationListener(interval) gps_added = addGpsLocationListener(interval)
gps_added = true
} }
else if (gpsLocationListenerRegistered) else if (gpsLocationListenerRegistered)
{ {
removeGpsLocationListener() removeGpsLocationListener()
} }
if (use_network_location && interval != LOCATION_INTERVAL_GIVE_UP) if (use_network_location && interval != Keys.LOCATION_INTERVAL_GIVE_UP)
{ {
addNetworkLocationListener(interval) network_added = addNetworkLocationListener(interval)
network_added = true
} }
else if (networkLocationListenerRegistered) else if (networkLocationListenerRegistered)
{ {
@ -219,7 +206,9 @@ class TrackerService: Service()
else else
{ {
listeners_enabled_at = 0 listeners_enabled_at = 0
location_interval = Keys.LOCATION_INTERVAL_GIVE_UP
} }
displayNotification()
} }
private fun createLocationListener(): LocationListener private fun createLocationListener(): LocationListener
@ -277,16 +266,20 @@ class TrackerService: Service()
Log.i("VOUSSOIR", "Arrived at home.") Log.i("VOUSSOIR", "Arrived at home.")
arrived_at_home = System.currentTimeMillis() arrived_at_home = System.currentTimeMillis()
} }
else if (location_interval == LOCATION_INTERVAL_SLEEP || significant_motion_sensor == null) else if (location_interval == Keys.LOCATION_INTERVAL_SLEEP)
{ {
// If we are already asleep, do not reset the listeners again because // If we are already asleep, do not reset the listeners again because
// that immediately fetches a new location. // that immediately fetches a new location.
// If we cannot rely on the motion sensor, then don't sleep!
} }
else if (allow_sleep && (System.currentTimeMillis() - arrived_at_home) > Keys.ONE_MINUTE_IN_MILLISECONDS) else if (
allow_sleep &&
has_motion_sensor &&
(System.currentTimeMillis() - arrived_at_home) > 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=LOCATION_INTERVAL_SLEEP) reset_location_listeners(interval=Keys.LOCATION_INTERVAL_SLEEP)
} }
return return
} }
@ -295,7 +288,7 @@ class TrackerService: Service()
{ {
Log.i("VOUSSOIR", "Leaving home.") Log.i("VOUSSOIR", "Leaving home.")
arrived_at_home = 0 arrived_at_home = 0
reset_location_listeners(interval=LOCATION_INTERVAL_FULL_POWER) reset_location_listeners(interval=Keys.LOCATION_INTERVAL_FULL_POWER)
} }
if (! isRecentEnough(location)) if (! isRecentEnough(location))
@ -365,31 +358,41 @@ class TrackerService: Service()
private fun displayNotification(): Notification private fun displayNotification(): Notification
{ {
val timestamp = iso8601_local_noms(currentBestLocation.time) notification_builder.setWhen(currentBestLocation.time)
if (shouldCreateNotificationChannel()) if (shouldCreateNotificationChannel())
{ {
createNotificationChannel() createNotificationChannel()
} }
if (location_interval == LOCATION_INTERVAL_SLEEP) val timestamp = iso8601_local_noms(currentBestLocation.time)
if (trackingState == Keys.STATE_TRACKING_ACTIVE)
{ {
notification_builder.setContentText("${timestamp} (sleeping)") notification_builder.setContentTitle(this.getString(R.string.notification_title_trackbook_running))
if (location_interval == Keys.LOCATION_INTERVAL_FULL_POWER)
{
notification_builder.setContentTitle("${timestamp} (recording)")
notification_builder.setSmallIcon(R.drawable.ic_satellite_24dp)
}
else if (location_interval == Keys.LOCATION_INTERVAL_SLEEP)
{
notification_builder.setContentTitle("${timestamp} (sleeping)")
notification_builder.setSmallIcon(R.drawable.ic_sleep_24dp)
}
else if (location_interval == Keys.LOCATION_INTERVAL_GIVE_UP)
{
notification_builder.setContentTitle("${timestamp} (deadzone)")
notification_builder.setSmallIcon(R.drawable.ic_skull_24dp)
} }
else else
{ {
notification_builder.setContentText(timestamp) notification_builder.setContentText(timestamp)
notification_builder.setSmallIcon(R.drawable.ic_fiber_manual_record_inactive_24dp)
} }
notification_builder.setWhen(currentBestLocation.time)
if (trackingState == Keys.STATE_TRACKING_ACTIVE)
{
notification_builder.setContentTitle(this.getString(R.string.notification_title_trackbook_running))
notification_builder.setLargeIcon(AppCompatResources.getDrawable(this, R.drawable.ic_notification_icon_large_tracking_active_48dp)!!.toBitmap())
} }
else else
{ {
notification_builder.setContentTitle(this.getString(R.string.notification_title_trackbook_not_running)) notification_builder.setContentTitle("${timestamp} (stopped)")
notification_builder.setLargeIcon(AppCompatResources.getDrawable(this, R.drawable.ic_notification_icon_large_tracking_stopped_48dp)!!.toBitmap()) notification_builder.setSmallIcon(R.drawable.ic_fiber_manual_stop_24dp)
} }
val notification = notification_builder.build() val notification = notification_builder.build()
@ -452,8 +455,9 @@ class TrackerService: Service()
Log.i("VOUSSOIR", "TrackerService.onBind") Log.i("VOUSSOIR", "TrackerService.onBind")
if (listeners_enabled_at == 0L) if (listeners_enabled_at == 0L)
{ {
reset_location_listeners(interval=LOCATION_INTERVAL_FULL_POWER) reset_location_listeners(interval=Keys.LOCATION_INTERVAL_FULL_POWER)
} }
displayNotification()
bound = true bound = true
return binder return binder
} }
@ -464,8 +468,9 @@ class TrackerService: Service()
Log.i("VOUSSOIR", "TrackerService.onRebind") Log.i("VOUSSOIR", "TrackerService.onRebind")
if (listeners_enabled_at == 0L) if (listeners_enabled_at == 0L)
{ {
reset_location_listeners(interval=LOCATION_INTERVAL_FULL_POWER) reset_location_listeners(interval=Keys.LOCATION_INTERVAL_FULL_POWER)
} }
displayNotification()
bound = true bound = true
} }
@ -478,7 +483,7 @@ class TrackerService: Service()
// stop receiving location updates - if not tracking // stop receiving location updates - if not tracking
if (trackingState != Keys.STATE_TRACKING_ACTIVE) if (trackingState != Keys.STATE_TRACKING_ACTIVE)
{ {
reset_location_listeners(LOCATION_INTERVAL_GIVE_UP) reset_location_listeners(interval=Keys.LOCATION_INTERVAL_GIVE_UP)
} }
// ensures onRebind is called // ensures onRebind is called
return true return true
@ -516,6 +521,52 @@ class TrackerService: Service()
currentBestLocation = getLastKnownLocation(this) currentBestLocation = getLastKnownLocation(this)
PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener) PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener)
val vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
has_motion_sensor = false
sensor_manager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
significant_motion_sensor = sensor_manager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION)
if (significant_motion_sensor != null)
{
val significant_motion_listener = object : TriggerEventListener() {
override fun onTrigger(event: TriggerEvent?) {
Log.i("VOUSSOIR", "Significant motion")
last_significant_motion = System.currentTimeMillis()
if (trackingState == Keys.STATE_TRACKING_ACTIVE && location_interval != Keys.LOCATION_INTERVAL_FULL_POWER)
{
vibrator.vibrate(100)
reset_location_listeners(Keys.LOCATION_INTERVAL_FULL_POWER)
}
sensor_manager.requestTriggerSensor(this, significant_motion_sensor)
}
}
Log.i("VOUSSOIR", "Got significant motion sensor.")
sensor_manager.requestTriggerSensor(significant_motion_listener, significant_motion_sensor)
has_motion_sensor = true
}
step_counter_sensor = sensor_manager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
if (step_counter_sensor != null)
{
val step_counter_listener = object: SensorEventListener {
override fun onSensorChanged(event: SensorEvent?) {
Log.i("VOUSSOIR", "Step counter changed")
last_significant_motion = System.currentTimeMillis()
if (trackingState == Keys.STATE_TRACKING_ACTIVE && location_interval != Keys.LOCATION_INTERVAL_FULL_POWER)
{
// beeper.startTone(ToneGenerator.TONE_PROP_ACK, 150)
vibrator.vibrate(100)
reset_location_listeners(Keys.LOCATION_INTERVAL_FULL_POWER)
}
}
override fun onAccuracyChanged(p0: Sensor?, p1: Int) {
}
}
Log.i("VOUSSOIR", "Got step count sensor.")
sensor_manager.registerListener(step_counter_listener, step_counter_sensor, 5_000_000, 5_000_000)
has_motion_sensor = true
}
handler.post(background_watchdog) handler.post(background_watchdog)
} }
@ -524,28 +575,6 @@ class TrackerService: Service()
{ {
Log.i("VOUSSOIR", "TrackerService.onStartCommand") Log.i("VOUSSOIR", "TrackerService.onStartCommand")
val vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
sensor_manager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
significant_motion_sensor = sensor_manager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION)
if (significant_motion_sensor != null)
{
val triggerEventListener = object : TriggerEventListener() {
override fun onTrigger(event: TriggerEvent?) {
Log.i("VOUSSOIR", "Significant motion")
last_significant_motion = System.currentTimeMillis()
arrived_at_home = 0L
if (location_interval != LOCATION_INTERVAL_FULL_POWER)
{
// beeper.startTone(ToneGenerator.TONE_PROP_ACK, 150)
vibrator.vibrate(100)
reset_location_listeners(LOCATION_INTERVAL_FULL_POWER)
}
sensor_manager.requestTriggerSensor(this, significant_motion_sensor)
}
}
sensor_manager.requestTriggerSensor(triggerEventListener, significant_motion_sensor)
}
// SERVICE RESTART (via START_STICKY) // SERVICE RESTART (via START_STICKY)
if (intent == null) if (intent == null)
{ {
@ -580,7 +609,7 @@ class TrackerService: Service()
stopForeground(STOP_FOREGROUND_REMOVE) stopForeground(STOP_FOREGROUND_REMOVE)
notificationManager.cancel(Keys.TRACKER_SERVICE_NOTIFICATION_ID) // this call was not necessary prior to Android 12 notificationManager.cancel(Keys.TRACKER_SERVICE_NOTIFICATION_ID) // this call was not necessary prior to Android 12
PreferencesHelper.unregisterPreferenceChangeListener(sharedPreferenceChangeListener) PreferencesHelper.unregisterPreferenceChangeListener(sharedPreferenceChangeListener)
reset_location_listeners(LOCATION_INTERVAL_GIVE_UP) reset_location_listeners(interval=Keys.LOCATION_INTERVAL_GIVE_UP)
handler.removeCallbacks(background_watchdog) handler.removeCallbacks(background_watchdog)
} }
@ -588,7 +617,7 @@ class TrackerService: Service()
{ {
Log.i("VOUSSOIR", "TrackerService.startTracking") Log.i("VOUSSOIR", "TrackerService.startTracking")
arrived_at_home = 0 arrived_at_home = 0
reset_location_listeners(interval=LOCATION_INTERVAL_FULL_POWER) reset_location_listeners(interval=Keys.LOCATION_INTERVAL_FULL_POWER)
trackingState = Keys.STATE_TRACKING_ACTIVE trackingState = Keys.STATE_TRACKING_ACTIVE
PreferencesHelper.saveTrackingState(trackingState) PreferencesHelper.saveTrackingState(trackingState)
recent_displacement_locations.clear() recent_displacement_locations.clear()
@ -600,7 +629,7 @@ class TrackerService: Service()
Log.i("VOUSSOIR", "TrackerService.stopTracking") Log.i("VOUSSOIR", "TrackerService.stopTracking")
trackbook.database.commit() trackbook.database.commit()
arrived_at_home = 0 arrived_at_home = 0
reset_location_listeners(interval=LOCATION_INTERVAL_FULL_POWER) reset_location_listeners(interval=Keys.LOCATION_INTERVAL_FULL_POWER)
trackingState = Keys.STATE_TRACKING_STOPPED trackingState = Keys.STATE_TRACKING_STOPPED
PreferencesHelper.saveTrackingState(trackingState) PreferencesHelper.saveTrackingState(trackingState)
recent_displacement_locations.clear() recent_displacement_locations.clear()
@ -614,12 +643,12 @@ class TrackerService: Service()
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=LOCATION_INTERVAL_FULL_POWER) reset_location_listeners(interval=Keys.LOCATION_INTERVAL_FULL_POWER)
} }
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=LOCATION_INTERVAL_FULL_POWER) reset_location_listeners(interval=Keys.LOCATION_INTERVAL_FULL_POWER)
} }
Keys.PREF_USE_IMPERIAL_UNITS -> Keys.PREF_USE_IMPERIAL_UNITS ->
{ {
@ -632,9 +661,9 @@ class TrackerService: Service()
Keys.PREF_ALLOW_SLEEP -> Keys.PREF_ALLOW_SLEEP ->
{ {
allow_sleep = PreferencesHelper.loadAllowSleep() allow_sleep = PreferencesHelper.loadAllowSleep()
if (! allow_sleep && location_interval != LOCATION_INTERVAL_FULL_POWER) if (! allow_sleep && location_interval != Keys.LOCATION_INTERVAL_FULL_POWER)
{ {
reset_location_listeners(LOCATION_INTERVAL_FULL_POWER) reset_location_listeners(Keys.LOCATION_INTERVAL_FULL_POWER)
} }
} }
Keys.PREF_DEVICE_ID -> Keys.PREF_DEVICE_ID ->
@ -649,28 +678,19 @@ class TrackerService: Service()
override fun run() override fun run()
{ {
Log.i("VOUSSOIR", "TrackerService.background_watchdog") Log.i("VOUSSOIR", "TrackerService.background_watchdog")
handler.postDelayed(this, 30 * Keys.ONE_SECOND_IN_MILLISECONDS) handler.postDelayed(this, WATCHDOG_INTERVAL)
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
val struggletime: Long
if (location_interval == LOCATION_INTERVAL_FULL_POWER)
{
struggletime = 2 * Keys.ONE_MINUTE_IN_MILLISECONDS
}
else
{
struggletime = 4 * Keys.ONE_MINUTE_IN_MILLISECONDS
}
if ( if (
allow_sleep && allow_sleep &&
has_motion_sensor &&
trackingState == Keys.STATE_TRACKING_ACTIVE && trackingState == Keys.STATE_TRACKING_ACTIVE &&
location_interval != LOCATION_INTERVAL_GIVE_UP && location_interval != Keys.LOCATION_INTERVAL_GIVE_UP &&
significant_motion_sensor != null && (now - listeners_enabled_at) > TIME_UNTIL_GIVE_UP &&
(now - listeners_enabled_at) > struggletime && (now - currentBestLocation.time) > TIME_UNTIL_GIVE_UP &&
(now - currentBestLocation.time) > struggletime && (now - last_significant_motion) > TIME_UNTIL_GIVE_UP
(now - last_significant_motion) > struggletime
) )
{ {
reset_location_listeners(LOCATION_INTERVAL_GIVE_UP) reset_location_listeners(Keys.LOCATION_INTERVAL_GIVE_UP)
} }
} }
} }

View File

@ -34,12 +34,12 @@ fun create_start_end_markers(context: Context, map_view: MapView, startpoint: Tr
{ {
Log.i("VOUSSOIR", "MapOverlayHelper.create_start_end_markers") Log.i("VOUSSOIR", "MapOverlayHelper.create_start_end_markers")
val overlayItems: ArrayList<OverlayItem> = ArrayList<OverlayItem>() val overlayItems: ArrayList<OverlayItem> = ArrayList<OverlayItem>()
val startmarker: OverlayItem = createOverlayItem(context, startpoint.latitude, startpoint.longitude, startpoint.accuracy, startpoint.provider, startpoint.time) val startmarker: OverlayItem = createOverlayItem(startpoint.latitude, startpoint.longitude, title="Start", iso8601_local_noms(startpoint.time))
startmarker.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_start_48dp)!!) startmarker.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_start_48dp)!!)
overlayItems.add(startmarker) overlayItems.add(startmarker)
if (startpoint != endpoint) if (startpoint != endpoint)
{ {
val endmarker: OverlayItem = createOverlayItem(context, endpoint.latitude, endpoint.longitude, endpoint.accuracy, endpoint.provider, endpoint.time) val endmarker: OverlayItem = createOverlayItem(endpoint.latitude, endpoint.longitude, title="End", description= iso8601_local_noms(endpoint.time))
endmarker.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_end_48dp)!!) endmarker.setMarker(ContextCompat.getDrawable(context, R.drawable.ic_marker_track_end_48dp)!!)
overlayItems.add(endmarker) overlayItems.add(endmarker)
} }
@ -48,10 +48,8 @@ fun create_start_end_markers(context: Context, map_view: MapView, startpoint: Tr
return overlay return overlay
} }
fun createOverlayItem(context: Context, latitude: Double, longitude: Double, accuracy: Float, provider: String, time: Long): OverlayItem fun createOverlayItem(latitude: Double, longitude: Double, title: String, description: String): OverlayItem
{ {
val title = "${context.getString(R.string.marker_description_time)}: ${SimpleDateFormat.getTimeInstance(SimpleDateFormat.MEDIUM, Locale.getDefault()).format(time)}"
val description = "${context.getString(R.string.marker_description_time)}: ${SimpleDateFormat.getTimeInstance(SimpleDateFormat.MEDIUM, Locale.getDefault()).format(time)} | ${context.getString(R.string.marker_description_accuracy)}: ${DecimalFormat("#0.00").format(accuracy)} (${provider})"
val position = GeoPoint(latitude, longitude) val position = GeoPoint(latitude, longitude)
val item = OverlayItem(title, description, position) val item = OverlayItem(title, description, position)
item.markerHotspot = OverlayItem.HotspotPlace.CENTER item.markerHotspot = OverlayItem.HotspotPlace.CENTER

View File

@ -97,19 +97,6 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
/> />
<ImageButton
android:id="@+id/power_level_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:src="@drawable/ic_satellite_24dp"
app:backgroundTint="@color/default_transparent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_satellite_24dp"
app:tint="@color/location_button_icon" />
<!-- GROUPS --> <!-- GROUPS -->
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>