1.1.1 - More fixes for notification reliability
This commit is contained in:
@@ -14,8 +14,8 @@ android {
|
||||
applicationId = "com.atridad.openclimb"
|
||||
minSdk = 33
|
||||
targetSdk = 36
|
||||
versionCode = 15
|
||||
versionName = "1.1.0"
|
||||
versionCode = 16
|
||||
versionName = "1.1.1"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
@@ -52,7 +51,8 @@
|
||||
android:name=".service.SessionTrackingService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="specialUse">
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:description="@string/session_tracking_service_description">
|
||||
<meta-data
|
||||
android:name="android.app.foreground_service_type"
|
||||
android:value="specialUse" />
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<resources>
|
||||
<string name="app_name">OpenClimb</string>
|
||||
<string name="session_tracking_service_description">Tracks active climbing sessions and displays session information in the notification area</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user