feat: foreground service for background message notifications
This commit is contained in:
parent
38394c6350
commit
c39bc5e485
4 changed files with 104 additions and 0 deletions
|
|
@ -4,6 +4,8 @@
|
|||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
|
||||
<application
|
||||
android:name=".BollwerkApp"
|
||||
|
|
@ -34,6 +36,11 @@
|
|||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
<service
|
||||
android:name=".service.MessagingService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
package de.bollwerk.app.service
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.os.IBinder
|
||||
import androidx.core.app.NotificationCompat
|
||||
import de.bollwerk.app.MainActivity
|
||||
import de.bollwerk.app.R
|
||||
|
||||
/**
|
||||
* Foreground Service der den Prozess am Leben hält, damit die WebSocket-Verbindung
|
||||
* auch im Hintergrund bestehen bleibt und Nachrichten empfangen werden können.
|
||||
*/
|
||||
class MessagingService : Service() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
createServiceChannel()
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
when (intent?.action) {
|
||||
ACTION_STOP -> {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
}
|
||||
startForeground(NOTIFICATION_ID, buildNotification(), ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
|
||||
private fun createServiceChannel() {
|
||||
val channel = NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
"Verbindung",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
description = "Hält die Verbindung zum Server für Nachrichten aufrecht"
|
||||
setShowBadge(false)
|
||||
}
|
||||
val manager = getSystemService(NotificationManager::class.java)
|
||||
manager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
private fun buildNotification(): Notification {
|
||||
val openIntent = PendingIntent.getActivity(
|
||||
this,
|
||||
0,
|
||||
Intent(this, MainActivity::class.java),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
return NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_notification_message)
|
||||
.setContentTitle("Bollwerk")
|
||||
.setContentText("Verbunden – Nachrichten werden empfangen")
|
||||
.setOngoing(true)
|
||||
.setContentIntent(openIntent)
|
||||
.build()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CHANNEL_ID = "bollwerk_service"
|
||||
private const val NOTIFICATION_ID = 9001
|
||||
const val ACTION_STOP = "de.bollwerk.app.STOP_MESSAGING_SERVICE"
|
||||
|
||||
fun start(context: Context) {
|
||||
val intent = Intent(context, MessagingService::class.java)
|
||||
context.startForegroundService(intent)
|
||||
}
|
||||
|
||||
fun stop(context: Context) {
|
||||
val intent = Intent(context, MessagingService::class.java).apply {
|
||||
action = ACTION_STOP
|
||||
}
|
||||
context.startService(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
package de.bollwerk.app.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import de.bollwerk.app.data.sync.WebSocketClient
|
||||
import de.bollwerk.app.data.sync.WebSocketEvent
|
||||
import de.bollwerk.app.domain.AuthEventBus
|
||||
|
|
@ -11,6 +13,7 @@ import de.bollwerk.app.domain.model.SettingsKey.StringKey
|
|||
import de.bollwerk.app.domain.repository.ImportExportRepository
|
||||
import de.bollwerk.app.domain.repository.SettingsRepository
|
||||
import de.bollwerk.app.domain.repository.SyncService
|
||||
import de.bollwerk.app.service.MessagingService
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
|
|
@ -25,6 +28,7 @@ internal class MainViewModel @Inject constructor(
|
|||
private val webSocketClient: WebSocketClient,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val importExportRepository: ImportExportRepository,
|
||||
@ApplicationContext private val context: Context
|
||||
) : ViewModel() {
|
||||
|
||||
private val _navigateToSettings = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
|
||||
|
|
@ -45,6 +49,7 @@ internal class MainViewModel @Inject constructor(
|
|||
if (token.isNotBlank() && serverUrl.isNotBlank()) {
|
||||
Log.i(TAG, "App-Start: Token vorhanden – verbinde WebSocket")
|
||||
webSocketClient.connect(serverUrl, token)
|
||||
MessagingService.start(context)
|
||||
} else {
|
||||
Log.d(TAG, "App-Start: Kein Token – kein WebSocket")
|
||||
}
|
||||
|
|
@ -57,6 +62,7 @@ internal class MainViewModel @Inject constructor(
|
|||
authEventBus.loginSuccess.collect { (serverUrl, token) ->
|
||||
Log.i(TAG, "Login erfolgreich – verbinde WebSocket")
|
||||
webSocketClient.connect(serverUrl, token)
|
||||
MessagingService.start(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -68,6 +74,7 @@ internal class MainViewModel @Inject constructor(
|
|||
Log.w(TAG, "Session abgelaufen – Forced-Logout")
|
||||
syncService.logout()
|
||||
webSocketClient.disconnect()
|
||||
MessagingService.stop(context)
|
||||
_navigateToSettings.emit(Unit)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import de.bollwerk.app.domain.model.toJson
|
|||
import de.bollwerk.app.domain.repository.ImportExportRepository
|
||||
import de.bollwerk.app.domain.repository.SettingsRepository
|
||||
import de.bollwerk.app.domain.repository.SyncService
|
||||
import de.bollwerk.app.service.MessagingService
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -230,6 +231,7 @@ internal class SettingsViewModel @Inject constructor(
|
|||
viewModelScope.launch {
|
||||
syncService.logout()
|
||||
webSocketClient.disconnect()
|
||||
MessagingService.stop(context)
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
isLoggedIn = false,
|
||||
|
|
|
|||
Loading…
Reference in a new issue