checkpoint

This commit is contained in:
voussoir 2023-03-24 17:15:33 -07:00
parent c434d3b840
commit d8d9bc23ff
2 changed files with 165 additions and 234 deletions

View file

@ -31,16 +31,20 @@ import android.location.Location
import android.location.LocationListener import android.location.LocationListener
import android.location.LocationManager import android.location.LocationManager
import android.Manifest import android.Manifest
import android.app.NotificationChannel
import android.app.PendingIntent
import android.app.TaskStackBuilder
import android.os.* import android.os.*
import android.util.Log import android.util.Log
import androidx.annotation.RequiresApi
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import org.osmdroid.util.GeoPoint import org.osmdroid.util.GeoPoint
import java.util.* import java.util.*
import net.voussoir.trkpt.helpers.* import net.voussoir.trkpt.helpers.*
/*
* TrackerService class
*/
class TrackerService: Service() class TrackerService: Service()
{ {
lateinit var trackbook: Trackbook lateinit var trackbook: Trackbook
@ -50,18 +54,16 @@ class TrackerService: Service()
var omitRests: Boolean = true var omitRests: 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 last_location_time: Long = 0
var lastCommit: Long = 0 var lastCommit: Long = 0
var location_min_time_ms: Long = 0 var location_min_time_ms: Long = 0
private val RECENT_TRKPT_COUNT = 7200 private val RECENT_TRKPT_COUNT = 7200
lateinit var recent_trkpts: Deque<Trkpt>
lateinit var recent_displacement_locations: Deque<Location> lateinit var recent_displacement_locations: Deque<Location>
lateinit var recent_trackpoints_for_mapview: MutableList<GeoPoint> lateinit var recent_trackpoints_for_mapview: MutableList<GeoPoint>
var bound: Boolean = false var bound: Boolean = false
private val binder = LocalBinder() private val binder = LocalBinder()
private lateinit var notificationManager: NotificationManager private lateinit var notificationManager: NotificationManager
private lateinit var notificationHelper: NotificationHelper private lateinit var notification_builder: NotificationCompat.Builder
private lateinit var locationManager: LocationManager private lateinit var locationManager: LocationManager
private lateinit var gpsLocationListener: LocationListener private lateinit var gpsLocationListener: LocationListener
@ -151,6 +153,34 @@ class TrackerService: Service()
Log.i("VOUSSOIR", "Added Network location listener.") Log.i("VOUSSOIR", "Added Network location listener.")
} }
fun removeGpsLocationListener()
{
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)
{
locationManager.removeUpdates(gpsLocationListener)
gpsLocationListenerRegistered = false
Log.i("VOUSSOIR", "Removed GPS location listener.")
}
else
{
Log.w("VOUSSOIR", "Unable to remove GPS location listener. Location permission is needed.")
}
}
fun removeNetworkLocationListener()
{
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)
{
locationManager.removeUpdates(networkLocationListener)
networkLocationListenerRegistered = false
Log.i("VOUSSOIR", "Removed Network location listener.")
}
else
{
Log.w("VOUSSOIR", "Unable to remove Network location listener. Location permission is needed.")
}
}
private fun createLocationListener(): LocationListener private fun createLocationListener(): LocationListener
{ {
return object : LocationListener return object : LocationListener
@ -223,12 +253,6 @@ class TrackerService: Service()
val trkpt = Trkpt(device_id=device_id, location=location) val trkpt = Trkpt(device_id=device_id, location=location)
trackbook.database.insert_trkpt(trkpt) trackbook.database.insert_trkpt(trkpt)
recent_trkpts.add(trkpt)
while (recent_trkpts.size > RECENT_TRKPT_COUNT)
{
recent_trkpts.removeFirst()
}
recent_trackpoints_for_mapview.add(trkpt) recent_trackpoints_for_mapview.add(trkpt)
while (recent_trackpoints_for_mapview.size > RECENT_TRKPT_COUNT) while (recent_trackpoints_for_mapview.size > RECENT_TRKPT_COUNT)
{ {
@ -266,72 +290,92 @@ class TrackerService: Service()
} }
} }
/* Displays or updates notification */
private fun displayNotification(): Notification private fun displayNotification(): Notification
{ {
val notification: Notification = notificationHelper.createNotification( val timestamp = iso8601(currentBestLocation.time)
trackingState, if (shouldCreateNotificationChannel())
iso8601(GregorianCalendar.getInstance().time) {
) createNotificationChannel()
}
notification_builder.setContentText(timestamp)
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
{
notification_builder.setContentTitle(this.getString(R.string.notification_title_trackbook_not_running))
notification_builder.setLargeIcon(AppCompatResources.getDrawable(this, R.drawable.ic_notification_icon_large_tracking_stopped_48dp)!!.toBitmap())
}
val notification = notification_builder.build()
notificationManager.notify(Keys.TRACKER_SERVICE_NOTIFICATION_ID, notification) notificationManager.notify(Keys.TRACKER_SERVICE_NOTIFICATION_ID, notification)
return notification return notification
} }
/* Checks if notification channel should be created */
private fun shouldCreateNotificationChannel() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !nowPlayingChannelExists()
/* Checks if notification channel exists */
@RequiresApi(Build.VERSION_CODES.O)
private fun nowPlayingChannelExists() = notificationManager.getNotificationChannel(Keys.NOTIFICATION_CHANNEL_RECORDING) != null
/* Create a notification channel */
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel()
{
val notificationChannel = NotificationChannel(
Keys.NOTIFICATION_CHANNEL_RECORDING,
this.getString(R.string.notification_channel_recording_name),
NotificationManager.IMPORTANCE_LOW
).apply { description = this@TrackerService.getString(R.string.notification_channel_recording_description) }
notificationManager.createNotificationChannel(notificationChannel)
}
/* Notification pending intents */
// private val stopActionPendingIntent = PendingIntent.getService(
// this,
// 14,
// Intent(this, TrackerService::class.java).setAction(Keys.ACTION_STOP),
// PendingIntent.FLAG_IMMUTABLE
// )
// private val resumeActionPendingIntent = PendingIntent.getService(
// this,
// 16,
// Intent(this, TrackerService::class.java).setAction(Keys.ACTION_START),
// PendingIntent.FLAG_IMMUTABLE
// )
/* Notification actions */
// private val stopAction = NotificationCompat.Action(
// R.drawable.ic_notification_action_stop_24dp,
// this.getString(R.string.notification_pause),
// stopActionPendingIntent
// )
// private val resumeAction = NotificationCompat.Action(
// R.drawable.ic_notification_action_resume_36dp,
// this.getString(R.string.notification_resume),
// resumeActionPendingIntent
// )
// private val showAction = NotificationCompat.Action(
// R.drawable.ic_notification_action_show_36dp,
// this.getString(R.string.notification_show),
// showActionPendingIntent
// )
/* 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")
bound = true bound = true
// start receiving location updates
addGpsLocationListener() addGpsLocationListener()
addNetworkLocationListener() addNetworkLocationListener()
// return reference to this service
return binder return binder
} }
/* Overrides onCreate from Service */
override fun onCreate()
{
super.onCreate()
Log.i("VOUSSOIR", "TrackerService.onCreate")
trackbook = (applicationContext as Trackbook)
trackbook.load_homepoints()
recent_trkpts = ArrayDeque<Trkpt>(RECENT_TRKPT_COUNT)
recent_displacement_locations = ArrayDeque<Location>(5)
recent_trackpoints_for_mapview = mutableListOf()
use_gps_location = PreferencesHelper.load_location_gps()
use_network_location = PreferencesHelper.load_location_network()
device_id = PreferencesHelper.load_device_id()
useImperial = PreferencesHelper.loadUseImperialUnits()
omitRests = PreferencesHelper.loadOmitRests()
locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationHelper = NotificationHelper(this)
gpsProviderActive = isGpsEnabled(locationManager)
networkProviderActive = isNetworkEnabled(locationManager)
gpsLocationListener = createLocationListener()
networkLocationListener = createLocationListener()
trackingState = PreferencesHelper.loadTrackingState()
currentBestLocation = getLastKnownLocation(this)
PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener)
}
/* Overrides onDestroy from Service */
override fun onDestroy()
{
Log.i("VOUSSOIR", "TrackerService.onDestroy.")
super.onDestroy()
if (trackingState == Keys.STATE_TRACKING_ACTIVE)
{
stopTracking()
}
stopForeground(STOP_FOREGROUND_REMOVE)
notificationManager.cancel(Keys.TRACKER_SERVICE_NOTIFICATION_ID) // this call was not necessary prior to Android 12
PreferencesHelper.unregisterPreferenceChangeListener(sharedPreferenceChangeListener)
removeGpsLocationListener()
removeNetworkLocationListener()
}
/* Overrides onRebind from Service */ /* Overrides onRebind from Service */
override fun onRebind(intent: Intent?) override fun onRebind(intent: Intent?)
{ {
@ -341,6 +385,54 @@ class TrackerService: Service()
addNetworkLocationListener() addNetworkLocationListener()
} }
/* Overrides onUnbind from Service */
override fun onUnbind(intent: Intent?): Boolean
{
super.onUnbind(intent)
Log.i("VOUSSOIR", "TrackerService.onUnbind")
bound = false
// stop receiving location updates - if not tracking
if (trackingState != Keys.STATE_TRACKING_ACTIVE)
{
removeGpsLocationListener()
removeNetworkLocationListener()
}
// ensures onRebind is called
return true
}
/* Overrides onCreate from Service */
override fun onCreate()
{
super.onCreate()
Log.i("VOUSSOIR", "TrackerService.onCreate")
trackbook = (applicationContext as Trackbook)
trackbook.load_homepoints()
recent_displacement_locations = ArrayDeque<Location>(5)
recent_trackpoints_for_mapview = mutableListOf()
use_gps_location = PreferencesHelper.load_location_gps()
use_network_location = PreferencesHelper.load_location_network()
device_id = PreferencesHelper.load_device_id()
useImperial = PreferencesHelper.loadUseImperialUnits()
omitRests = PreferencesHelper.loadOmitRests()
locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notification_builder = NotificationCompat.Builder(this, Keys.NOTIFICATION_CHANNEL_RECORDING)
val showActionPendingIntent: PendingIntent? = TaskStackBuilder.create(this).run {
addNextIntentWithParentStack(Intent(this@TrackerService, MainActivity::class.java))
getPendingIntent(10, PendingIntent.FLAG_IMMUTABLE)
}
notification_builder.setContentIntent(showActionPendingIntent)
notification_builder.setSmallIcon(R.drawable.ic_notification_icon_small_24dp)
gpsProviderActive = isGpsEnabled(locationManager)
networkProviderActive = isNetworkEnabled(locationManager)
gpsLocationListener = createLocationListener()
networkLocationListener = createLocationListener()
trackingState = PreferencesHelper.loadTrackingState()
currentBestLocation = getLastKnownLocation(this)
PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener)
}
/* Overrides onStartCommand from Service */ /* Overrides onStartCommand from Service */
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int
{ {
@ -367,50 +459,21 @@ class TrackerService: Service()
return START_STICKY return START_STICKY
} }
/* Overrides onUnbind from Service */ /* Overrides onDestroy from Service */
override fun onUnbind(intent: Intent?): Boolean override fun onDestroy()
{ {
super.onUnbind(intent) Log.i("VOUSSOIR", "TrackerService.onDestroy.")
Log.i("VOUSSOIR", "TrackerService.onUnbind") super.onDestroy()
bound = false if (trackingState == Keys.STATE_TRACKING_ACTIVE)
// stop receiving location updates - if not tracking
if (trackingState != Keys.STATE_TRACKING_ACTIVE)
{ {
stopTracking()
}
stopForeground(STOP_FOREGROUND_REMOVE)
notificationManager.cancel(Keys.TRACKER_SERVICE_NOTIFICATION_ID) // this call was not necessary prior to Android 12
PreferencesHelper.unregisterPreferenceChangeListener(sharedPreferenceChangeListener)
removeGpsLocationListener() removeGpsLocationListener()
removeNetworkLocationListener() removeNetworkLocationListener()
} }
// ensures onRebind is called
return true
}
/* Adds location listeners to location manager */
fun removeGpsLocationListener()
{
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)
{
locationManager.removeUpdates(gpsLocationListener)
gpsLocationListenerRegistered = false
Log.i("VOUSSOIR", "Removed GPS location listener.")
}
else
{
Log.w("VOUSSOIR", "Unable to remove GPS location listener. Location permission is needed.")
}
}
fun removeNetworkLocationListener()
{
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)
{
locationManager.removeUpdates(networkLocationListener)
networkLocationListenerRegistered = false
Log.i("VOUSSOIR", "Removed Network location listener.")
}
else
{
Log.w("VOUSSOIR", "Unable to remove Network location listener. Location permission is needed.")
}
}
fun startTracking() fun startTracking()
{ {
@ -476,17 +539,8 @@ class TrackerService: Service()
} }
} }
} }
/*
* End of declaration
*/
/*
* Inner class: Local Binder that returns this service
*/
inner class LocalBinder : Binder() { inner class LocalBinder : Binder() {
val service: TrackerService = this@TrackerService val service: TrackerService = this@TrackerService
} }
/*
* End of inner class
*/
} }

View file

@ -1,123 +0,0 @@
/*
* NotificationHelper.kt
* Implements the NotificationHelper class
* A NotificationHelper creates and configures a notification
*
* 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
*/
package net.voussoir.trkpt.helpers
import android.app.*
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.app.NotificationCompat
import androidx.core.graphics.drawable.toBitmap
import net.voussoir.trkpt.Keys
import net.voussoir.trkpt.MainActivity
import net.voussoir.trkpt.R
import net.voussoir.trkpt.TrackerService
/*
* NotificationHelper class
*/
class NotificationHelper(private val trackerService: TrackerService)
{
/* Main class variables */
private val notificationManager: NotificationManager = trackerService.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
/* Creates notification */
fun createNotification(trackingState: Int, timestamp: String): Notification {
if (shouldCreateNotificationChannel())
{
createNotificationChannel()
}
// Build notification
val builder = NotificationCompat.Builder(trackerService, Keys.NOTIFICATION_CHANNEL_RECORDING)
builder.setContentIntent(showActionPendingIntent)
builder.setSmallIcon(R.drawable.ic_notification_icon_small_24dp)
builder.setContentText(timestamp)
// add icon and actions for stop, resume and show
when (trackingState) {
Keys.STATE_TRACKING_ACTIVE -> {
builder.setContentTitle(trackerService.getString(R.string.notification_title_trackbook_running))
builder.addAction(stopAction)
builder.setLargeIcon(AppCompatResources.getDrawable(trackerService, R.drawable.ic_notification_icon_large_tracking_active_48dp)!!.toBitmap())
}
else -> {
builder.setContentTitle(trackerService.getString(R.string.notification_title_trackbook_not_running))
builder.addAction(resumeAction)
builder.addAction(showAction)
builder.setLargeIcon(AppCompatResources.getDrawable(trackerService, R.drawable.ic_notification_icon_large_tracking_stopped_48dp)!!.toBitmap())
}
}
return builder.build()
}
/* Checks if notification channel should be created */
private fun shouldCreateNotificationChannel() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !nowPlayingChannelExists()
/* Checks if notification channel exists */
@RequiresApi(Build.VERSION_CODES.O)
private fun nowPlayingChannelExists() = notificationManager.getNotificationChannel(Keys.NOTIFICATION_CHANNEL_RECORDING) != null
/* Create a notification channel */
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel() {
val notificationChannel = NotificationChannel(Keys.NOTIFICATION_CHANNEL_RECORDING,
trackerService.getString(R.string.notification_channel_recording_name),
NotificationManager.IMPORTANCE_LOW)
.apply { description = trackerService.getString(R.string.notification_channel_recording_description) }
notificationManager.createNotificationChannel(notificationChannel)
}
/* Notification pending intents */
private val stopActionPendingIntent = PendingIntent.getService(
trackerService,
14,
Intent(trackerService, TrackerService::class.java).setAction(Keys.ACTION_STOP),
PendingIntent.FLAG_IMMUTABLE
)
private val resumeActionPendingIntent = PendingIntent.getService(
trackerService,
16,
Intent(trackerService, TrackerService::class.java).setAction(Keys.ACTION_START),
PendingIntent.FLAG_IMMUTABLE
)
private val showActionPendingIntent: PendingIntent? = TaskStackBuilder.create(trackerService).run {
addNextIntentWithParentStack(Intent(trackerService, MainActivity::class.java))
getPendingIntent(10, PendingIntent.FLAG_IMMUTABLE)
}
/* Notification actions */
private val stopAction = NotificationCompat.Action(
R.drawable.ic_notification_action_stop_24dp,
trackerService.getString(R.string.notification_pause),
stopActionPendingIntent
)
private val resumeAction = NotificationCompat.Action(
R.drawable.ic_notification_action_resume_36dp,
trackerService.getString(R.string.notification_resume),
resumeActionPendingIntent
)
private val showAction = NotificationCompat.Action(
R.drawable.ic_notification_action_show_36dp,
trackerService.getString(R.string.notification_show),
showActionPendingIntent
)
}