forked from thijooree/android
175 lines
6.6 KiB
Kotlin
175 lines
6.6 KiB
Kotlin
package sh.sar.basedbank.service
|
|
|
|
import android.app.Notification
|
|
import android.app.NotificationChannel
|
|
import android.app.NotificationManager
|
|
import android.app.PendingIntent
|
|
import android.app.Service
|
|
import android.content.Intent
|
|
import android.os.IBinder
|
|
import kotlinx.coroutines.CoroutineScope
|
|
import kotlinx.coroutines.Dispatchers
|
|
import kotlinx.coroutines.SupervisorJob
|
|
import kotlinx.coroutines.cancel
|
|
import kotlinx.coroutines.delay
|
|
import kotlinx.coroutines.isActive
|
|
import kotlinx.coroutines.launch
|
|
import sh.sar.basedbank.BasedBankApp
|
|
import sh.sar.basedbank.R
|
|
import sh.sar.basedbank.api.bml.BmlNotificationsClient
|
|
import sh.sar.basedbank.api.mib.MibActivityHistoryClient
|
|
import sh.sar.basedbank.ui.home.AppNotification
|
|
import sh.sar.basedbank.util.NotificationsCache
|
|
import java.util.concurrent.atomic.AtomicInteger
|
|
|
|
class NotificationPollingService : Service() {
|
|
|
|
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
|
private val app get() = application as BasedBankApp
|
|
private val notifIdCounter = AtomicInteger(2000)
|
|
|
|
override fun onBind(intent: Intent?): IBinder? = null
|
|
|
|
override fun onCreate() {
|
|
super.onCreate()
|
|
createChannels()
|
|
startForeground(SERVICE_NOTIF_ID, buildServiceNotification())
|
|
startPolling()
|
|
}
|
|
|
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int = START_STICKY
|
|
|
|
override fun onDestroy() {
|
|
scope.cancel()
|
|
super.onDestroy()
|
|
}
|
|
|
|
private fun startPolling() {
|
|
scope.launch {
|
|
while (isActive) {
|
|
runCatching { poll() }
|
|
delay(POLL_INTERVAL_MS)
|
|
}
|
|
}
|
|
}
|
|
|
|
private suspend fun poll() {
|
|
pollBml()
|
|
pollMib()
|
|
}
|
|
|
|
private suspend fun pollBml() {
|
|
val sessions = app.bmlSessions.toMap()
|
|
if (sessions.isEmpty()) return
|
|
val client = BmlNotificationsClient()
|
|
sessions.forEach { (loginId, session) ->
|
|
val result = try { client.fetchNotifications(session, loginId, page = 1) }
|
|
catch (_: Exception) { return@forEach }
|
|
if (result.items.isEmpty()) return@forEach
|
|
|
|
val cached = NotificationsCache.loadBml(this@NotificationPollingService, loginId)
|
|
if (cached.isEmpty()) {
|
|
NotificationsCache.saveBml(this@NotificationPollingService, loginId, result.items)
|
|
return@forEach
|
|
}
|
|
val cachedIds = cached.map { it.id }.toSet()
|
|
val newItems = result.items.filter { it.id !in cachedIds }
|
|
if (newItems.isNotEmpty()) {
|
|
NotificationsCache.saveBml(this@NotificationPollingService, loginId, result.items)
|
|
val channelId = ensureLoginChannel("BML", loginId)
|
|
newItems.forEach { postBankNotification(it, channelId) }
|
|
}
|
|
}
|
|
}
|
|
|
|
private suspend fun pollMib() {
|
|
val sessions = app.mibSessions.toMap()
|
|
if (sessions.isEmpty()) return
|
|
val client = MibActivityHistoryClient()
|
|
sessions.forEach { (loginId, session) ->
|
|
val result = try { client.fetchActivity(session, loginId, 1, 100) }
|
|
catch (_: Exception) { return@forEach }
|
|
|
|
val readIds = NotificationsCache.getMibReadIds(this@NotificationPollingService)
|
|
val cached = NotificationsCache.loadMib(this@NotificationPollingService, loginId, readIds)
|
|
if (cached.isEmpty()) {
|
|
NotificationsCache.saveMib(this@NotificationPollingService, loginId, result.items)
|
|
return@forEach
|
|
}
|
|
val cachedIds = cached.map { it.id }.toSet()
|
|
val newItems = result.items.filter { it.id !in cachedIds }
|
|
if (newItems.isNotEmpty()) {
|
|
val all = (cached + newItems).sortedByDescending { it.timestampMs }
|
|
NotificationsCache.saveMib(this@NotificationPollingService, loginId, all)
|
|
val channelId = ensureLoginChannel("MIB", loginId)
|
|
newItems.forEach { postBankNotification(it, channelId) }
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun ensureLoginChannel(bank: String, loginId: String): String {
|
|
val channelId = "bank_${bank.lowercase()}_$loginId"
|
|
val nm = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
|
if (nm.getNotificationChannel(channelId) == null) {
|
|
val profileName = when (bank) {
|
|
"BML" -> app.bmlProfilesMap[loginId]?.firstOrNull()?.name
|
|
"MIB" -> app.mibProfilesMap[loginId]?.firstOrNull()?.name
|
|
else -> null
|
|
} ?: loginId
|
|
nm.createNotificationChannel(
|
|
NotificationChannel(channelId, "$bank · $profileName", NotificationManager.IMPORTANCE_DEFAULT)
|
|
)
|
|
}
|
|
return channelId
|
|
}
|
|
|
|
private fun postBankNotification(notif: AppNotification, channelId: String) {
|
|
val nm = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
|
val pi = PendingIntent.getActivity(
|
|
this, 0,
|
|
packageManager.getLaunchIntentForPackage(packageName),
|
|
PendingIntent.FLAG_IMMUTABLE
|
|
)
|
|
val n = Notification.Builder(this, channelId)
|
|
.setSmallIcon(R.drawable.ic_bell)
|
|
.setContentTitle(notif.title)
|
|
.setContentText(notif.message)
|
|
.setContentIntent(pi)
|
|
.setAutoCancel(true)
|
|
.build()
|
|
nm.notify(notifIdCounter.getAndIncrement(), n)
|
|
}
|
|
|
|
private fun buildServiceNotification(): Notification {
|
|
val pi = PendingIntent.getActivity(
|
|
this, 0,
|
|
packageManager.getLaunchIntentForPackage(packageName),
|
|
PendingIntent.FLAG_IMMUTABLE
|
|
)
|
|
return Notification.Builder(this, CHANNEL_SERVICE)
|
|
.setSmallIcon(R.drawable.ic_bell)
|
|
.setContentTitle(getString(R.string.notif_service_title))
|
|
.setContentText(getString(R.string.notif_service_desc))
|
|
.setContentIntent(pi)
|
|
.setOngoing(true)
|
|
.build()
|
|
}
|
|
|
|
private fun createChannels() {
|
|
val nm = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
|
nm.createNotificationChannel(
|
|
NotificationChannel(
|
|
CHANNEL_SERVICE,
|
|
getString(R.string.notif_channel_service),
|
|
NotificationManager.IMPORTANCE_MIN
|
|
).apply { setShowBadge(false) }
|
|
)
|
|
}
|
|
|
|
companion object {
|
|
private const val POLL_INTERVAL_MS = 30_000L
|
|
private const val SERVICE_NOTIF_ID = 1001
|
|
const val CHANNEL_SERVICE = "notif_polling_service"
|
|
}
|
|
}
|