diff --git a/app/src/main/java/net/voussoir/trkpt/TrackerService.kt b/app/src/main/java/net/voussoir/trkpt/TrackerService.kt index c71fd4c..65c213c 100644 --- a/app/src/main/java/net/voussoir/trkpt/TrackerService.kt +++ b/app/src/main/java/net/voussoir/trkpt/TrackerService.kt @@ -31,16 +31,20 @@ import android.location.Location import android.location.LocationListener import android.location.LocationManager import android.Manifest +import android.app.NotificationChannel +import android.app.PendingIntent +import android.app.TaskStackBuilder import android.os.* 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.graphics.drawable.toBitmap import org.osmdroid.util.GeoPoint import java.util.* import net.voussoir.trkpt.helpers.* -/* - * TrackerService class - */ class TrackerService: Service() { lateinit var trackbook: Trackbook @@ -50,18 +54,16 @@ class TrackerService: Service() var omitRests: Boolean = true var device_id: String = random_device_id() var currentBestLocation: Location = getDefaultLocation() - var last_location_time: Long = 0 var lastCommit: Long = 0 var location_min_time_ms: Long = 0 private val RECENT_TRKPT_COUNT = 7200 - lateinit var recent_trkpts: Deque lateinit var recent_displacement_locations: Deque lateinit var recent_trackpoints_for_mapview: MutableList var bound: Boolean = false private val binder = LocalBinder() 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 gpsLocationListener: LocationListener @@ -151,6 +153,34 @@ class TrackerService: Service() 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 { return object : LocationListener @@ -223,12 +253,6 @@ class TrackerService: Service() val trkpt = Trkpt(device_id=device_id, location=location) 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) while (recent_trackpoints_for_mapview.size > RECENT_TRKPT_COUNT) { @@ -266,72 +290,92 @@ class TrackerService: Service() } } - /* Displays or updates notification */ private fun displayNotification(): Notification { - val notification: Notification = notificationHelper.createNotification( - trackingState, - iso8601(GregorianCalendar.getInstance().time) - ) + val timestamp = iso8601(currentBestLocation.time) + if (shouldCreateNotificationChannel()) + { + 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) 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 */ override fun onBind(p0: Intent?): IBinder { Log.i("VOUSSOIR", "TrackerService.onBind") bound = true - // start receiving location updates addGpsLocationListener() addNetworkLocationListener() - // return reference to this service 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(RECENT_TRKPT_COUNT) - recent_displacement_locations = ArrayDeque(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 */ override fun onRebind(intent: Intent?) { @@ -341,6 +385,54 @@ class TrackerService: Service() 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(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 */ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { @@ -367,49 +459,20 @@ class TrackerService: Service() return START_STICKY } - /* Overrides onUnbind from Service */ - override fun onUnbind(intent: Intent?): Boolean + /* Overrides onDestroy from Service */ + override fun onDestroy() { - super.onUnbind(intent) - Log.i("VOUSSOIR", "TrackerService.onUnbind") - bound = false - // stop receiving location updates - if not tracking - if (trackingState != Keys.STATE_TRACKING_ACTIVE) + Log.i("VOUSSOIR", "TrackerService.onDestroy.") + super.onDestroy() + if (trackingState == Keys.STATE_TRACKING_ACTIVE) { - removeGpsLocationListener() - 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.") + 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() } fun startTracking() @@ -476,17 +539,8 @@ class TrackerService: Service() } } } - /* - * End of declaration - */ - /* - * Inner class: Local Binder that returns this service - */ inner class LocalBinder : Binder() { val service: TrackerService = this@TrackerService } - /* - * End of inner class - */ } diff --git a/app/src/main/java/net/voussoir/trkpt/helpers/NotificationHelper.kt b/app/src/main/java/net/voussoir/trkpt/helpers/NotificationHelper.kt deleted file mode 100644 index 2b3f75e..0000000 --- a/app/src/main/java/net/voussoir/trkpt/helpers/NotificationHelper.kt +++ /dev/null @@ -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 - ) -} \ No newline at end of file