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.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
<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
|
<application
|
||||||
android:name=".BollwerkApp"
|
android:name=".BollwerkApp"
|
||||||
|
|
@ -34,6 +36,11 @@
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
android:resource="@xml/file_paths" />
|
android:resource="@xml/file_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".service.MessagingService"
|
||||||
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="dataSync" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</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
|
package de.bollwerk.app.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
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.WebSocketClient
|
||||||
import de.bollwerk.app.data.sync.WebSocketEvent
|
import de.bollwerk.app.data.sync.WebSocketEvent
|
||||||
import de.bollwerk.app.domain.AuthEventBus
|
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.ImportExportRepository
|
||||||
import de.bollwerk.app.domain.repository.SettingsRepository
|
import de.bollwerk.app.domain.repository.SettingsRepository
|
||||||
import de.bollwerk.app.domain.repository.SyncService
|
import de.bollwerk.app.domain.repository.SyncService
|
||||||
|
import de.bollwerk.app.service.MessagingService
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
|
@ -25,6 +28,7 @@ internal class MainViewModel @Inject constructor(
|
||||||
private val webSocketClient: WebSocketClient,
|
private val webSocketClient: WebSocketClient,
|
||||||
private val settingsRepository: SettingsRepository,
|
private val settingsRepository: SettingsRepository,
|
||||||
private val importExportRepository: ImportExportRepository,
|
private val importExportRepository: ImportExportRepository,
|
||||||
|
@ApplicationContext private val context: Context
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _navigateToSettings = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
|
private val _navigateToSettings = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
|
||||||
|
|
@ -45,6 +49,7 @@ internal class MainViewModel @Inject constructor(
|
||||||
if (token.isNotBlank() && serverUrl.isNotBlank()) {
|
if (token.isNotBlank() && serverUrl.isNotBlank()) {
|
||||||
Log.i(TAG, "App-Start: Token vorhanden – verbinde WebSocket")
|
Log.i(TAG, "App-Start: Token vorhanden – verbinde WebSocket")
|
||||||
webSocketClient.connect(serverUrl, token)
|
webSocketClient.connect(serverUrl, token)
|
||||||
|
MessagingService.start(context)
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "App-Start: Kein Token – kein WebSocket")
|
Log.d(TAG, "App-Start: Kein Token – kein WebSocket")
|
||||||
}
|
}
|
||||||
|
|
@ -57,6 +62,7 @@ internal class MainViewModel @Inject constructor(
|
||||||
authEventBus.loginSuccess.collect { (serverUrl, token) ->
|
authEventBus.loginSuccess.collect { (serverUrl, token) ->
|
||||||
Log.i(TAG, "Login erfolgreich – verbinde WebSocket")
|
Log.i(TAG, "Login erfolgreich – verbinde WebSocket")
|
||||||
webSocketClient.connect(serverUrl, token)
|
webSocketClient.connect(serverUrl, token)
|
||||||
|
MessagingService.start(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -68,6 +74,7 @@ internal class MainViewModel @Inject constructor(
|
||||||
Log.w(TAG, "Session abgelaufen – Forced-Logout")
|
Log.w(TAG, "Session abgelaufen – Forced-Logout")
|
||||||
syncService.logout()
|
syncService.logout()
|
||||||
webSocketClient.disconnect()
|
webSocketClient.disconnect()
|
||||||
|
MessagingService.stop(context)
|
||||||
_navigateToSettings.emit(Unit)
|
_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.ImportExportRepository
|
||||||
import de.bollwerk.app.domain.repository.SettingsRepository
|
import de.bollwerk.app.domain.repository.SettingsRepository
|
||||||
import de.bollwerk.app.domain.repository.SyncService
|
import de.bollwerk.app.domain.repository.SyncService
|
||||||
|
import de.bollwerk.app.service.MessagingService
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
@ -230,6 +231,7 @@ internal class SettingsViewModel @Inject constructor(
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
syncService.logout()
|
syncService.logout()
|
||||||
webSocketClient.disconnect()
|
webSocketClient.disconnect()
|
||||||
|
MessagingService.stop(context)
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isLoggedIn = false,
|
isLoggedIn = false,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue