diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9157e08..f5ee4fe 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -12,7 +12,6 @@
-
+ android:foregroundServiceType="specialUse"
+ android:description="@string/session_tracking_service_description">
diff --git a/app/src/main/java/com/atridad/openclimb/service/SessionTrackingService.kt b/app/src/main/java/com/atridad/openclimb/service/SessionTrackingService.kt
index 269a492..be28811 100644
--- a/app/src/main/java/com/atridad/openclimb/service/SessionTrackingService.kt
+++ b/app/src/main/java/com/atridad/openclimb/service/SessionTrackingService.kt
@@ -7,7 +7,6 @@ import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.IBinder
-import android.os.PowerManager
import androidx.core.app.NotificationCompat
import com.atridad.openclimb.MainActivity
import com.atridad.openclimb.R
@@ -17,13 +16,13 @@ import kotlinx.coroutines.*
import kotlinx.coroutines.flow.firstOrNull
import java.time.LocalDateTime
import java.time.temporal.ChronoUnit
+import kotlinx.coroutines.runBlocking
class SessionTrackingService : Service() {
private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private var notificationJob: Job? = null
private var monitoringJob: Job? = null
- private var wakeLock: PowerManager.WakeLock? = null
private lateinit var repository: ClimbRepository
private lateinit var notificationManager: NotificationManager
@@ -58,7 +57,6 @@ class SessionTrackingService : Service() {
notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
createNotificationChannel()
- acquireWakeLock()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@@ -87,59 +85,51 @@ class SessionTrackingService : Service() {
}
}
}
- // Return START_STICKY to restart service if it gets killed
- return START_STICKY
+
+ return START_REDELIVER_INTENT
}
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
- // If the app is removed from recent tasks, ensure the service keeps running
- // This helps maintain the notification even if the user swipes away the app
- }
-
- override fun onLowMemory() {
- super.onLowMemory()
- // Don't stop the service on low memory, just log it
- // The notification is important for user experience
}
override fun onBind(intent: Intent?): IBinder? = null
private fun startSessionTracking(sessionId: String) {
- // Cancel any existing jobs
notificationJob?.cancel()
monitoringJob?.cancel()
- // Start the main notification update job
+ try {
+ createAndShowNotification(sessionId)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+
notificationJob = serviceScope.launch {
try {
- // Initial notification update
- updateNotification(sessionId)
+ if (!isNotificationActive()) {
+ delay(1000L)
+ createAndShowNotification(sessionId)
+ }
- // Update every 2 seconds for better performance
while (isActive) {
- delay(2000L)
+ delay(5000L)
updateNotification(sessionId)
}
} catch (e: Exception) {
- // Log error and continue
e.printStackTrace()
}
}
- // Start the monitoring job that ensures notification stays active
monitoringJob = serviceScope.launch {
try {
while (isActive) {
- delay(5000L) // Check every 5 seconds
+ delay(10000L)
- // Verify the notification is still active
if (!isNotificationActive()) {
- // Notification was dismissed, recreate it
updateNotification(sessionId)
}
- // Verify the session is still active
val session = repository.getSessionById(sessionId)
if (session == null || session.status != com.atridad.openclimb.data.model.SessionStatus.ACTIVE) {
stopSessionTracking()
@@ -155,7 +145,6 @@ class SessionTrackingService : Service() {
private fun stopSessionTracking() {
notificationJob?.cancel()
monitoringJob?.cancel()
- releaseWakeLock()
stopForeground(STOP_FOREGROUND_REMOVE)
stopSelf()
}
@@ -171,14 +160,37 @@ class SessionTrackingService : Service() {
private suspend fun updateNotification(sessionId: String) {
try {
- val session = repository.getSessionById(sessionId)
+ createAndShowNotification(sessionId)
+ } catch (e: Exception) {
+ e.printStackTrace()
+
+ try {
+ delay(10000L)
+ createAndShowNotification(sessionId)
+ } catch (retryException: Exception) {
+ retryException.printStackTrace()
+ stopSessionTracking()
+ }
+ }
+ }
+
+ private fun createAndShowNotification(sessionId: String) {
+ try {
+ val session = runBlocking {
+ repository.getSessionById(sessionId)
+ }
if (session == null || session.status != com.atridad.openclimb.data.model.SessionStatus.ACTIVE) {
stopSessionTracking()
return
}
- val gym = repository.getGymById(session.gymId)
- val attempts = repository.getAttemptsBySession(sessionId).firstOrNull() ?: emptyList()
+ val gym = runBlocking {
+ repository.getGymById(session.gymId)
+ }
+
+ val attempts = runBlocking {
+ repository.getAttemptsBySession(sessionId).firstOrNull() ?: emptyList()
+ }
val duration = session.startTime?.let { startTime ->
try {
@@ -205,7 +217,7 @@ class SessionTrackingService : Service() {
.setSmallIcon(R.drawable.ic_mountains)
.setOngoing(true)
.setAutoCancel(false)
- .setPriority(NotificationCompat.PRIORITY_LOW)
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(createOpenAppIntent())
@@ -221,24 +233,13 @@ class SessionTrackingService : Service() {
)
.build()
- // Always start foreground to ensure service stays alive
startForeground(NOTIFICATION_ID, notification)
- // Also notify separately to ensure it's visible
notificationManager.notify(NOTIFICATION_ID, notification)
} catch (e: Exception) {
e.printStackTrace()
- // Don't stop the service on notification errors, just log them
- // Try to restart the notification after a delay
- try {
- delay(5000L)
- updateNotification(sessionId)
- } catch (retryException: Exception) {
- retryException.printStackTrace()
- // If retry fails, stop the service to prevent infinite loops
- stopSessionTracking()
- }
+ throw e
}
}
@@ -269,50 +270,23 @@ class SessionTrackingService : Service() {
val channel = NotificationChannel(
CHANNEL_ID,
"Session Tracking",
- NotificationManager.IMPORTANCE_LOW
+ NotificationManager.IMPORTANCE_DEFAULT
).apply {
description = "Shows active climbing session information"
setShowBadge(false)
lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
enableLights(false)
enableVibration(false)
+ setSound(null, null)
}
notificationManager.createNotificationChannel(channel)
}
- private fun acquireWakeLock() {
- try {
- val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
- wakeLock = powerManager.newWakeLock(
- PowerManager.PARTIAL_WAKE_LOCK,
- "OpenClimb:SessionTrackingWakeLock"
- ).apply {
- acquire(10*60*1000L) // 10 minutes timeout
- }
- } catch (e: Exception) {
- e.printStackTrace()
- }
- }
-
- private fun releaseWakeLock() {
- try {
- wakeLock?.let {
- if (it.isHeld) {
- it.release()
- }
- }
- wakeLock = null
- } catch (e: Exception) {
- e.printStackTrace()
- }
- }
-
override fun onDestroy() {
super.onDestroy()
notificationJob?.cancel()
monitoringJob?.cancel()
- releaseWakeLock()
serviceScope.cancel()
}
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 93d5beb..7ef7531 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,4 @@
OpenClimb
+ Tracks active climbing sessions and displays session information in the notification area
\ No newline at end of file