feat(android): add richer service notification

This commit is contained in:
Dark-Avery 2026-03-16 22:58:43 +03:00
parent db5a6cc696
commit 8d43fa25fa
2 changed files with 111 additions and 6 deletions

View File

@ -3,11 +3,13 @@ package org.flowseal.tgwsproxy
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.os.Build
import android.os.IBinder
import androidx.core.app.TaskStackBuilder
import androidx.core.app.NotificationCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -47,7 +49,14 @@ class ProxyForegroundService : Service() {
startForeground(
NOTIFICATION_ID,
buildNotification(
getString(R.string.notification_starting, config.host, config.port),
buildNotificationPayload(
config = config,
statusText = getString(
R.string.notification_starting,
config.host,
config.port,
),
),
),
)
serviceScope.launch {
@ -68,11 +77,21 @@ class ProxyForegroundService : Service() {
override fun onBind(intent: Intent?): IBinder? = null
private fun buildNotification(contentText: String): Notification {
private fun buildNotification(payload: NotificationPayload): Notification {
return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(getString(R.string.notification_title))
.setContentText(contentText)
.setContentText(payload.statusText)
.setSubText(payload.endpointText)
.setStyle(
NotificationCompat.BigTextStyle().bigText(payload.detailsText),
)
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setContentIntent(createOpenAppPendingIntent())
.addAction(
0,
getString(R.string.notification_action_stop),
createStopPendingIntent(),
)
.setOngoing(true)
.setOnlyAlertOnce(true)
.build()
@ -85,7 +104,16 @@ class ProxyForegroundService : Service() {
result.onSuccess {
ProxyServiceState.markStarted(config)
updateNotification(getString(R.string.notification_running, config.host, config.port))
updateNotification(
buildNotificationPayload(
config = config,
statusText = getString(
R.string.notification_running,
config.host,
config.port,
),
),
)
}.onFailure { error ->
ProxyServiceState.markFailed(
error.message ?: getString(R.string.proxy_start_failed_generic),
@ -107,9 +135,75 @@ class ProxyForegroundService : Service() {
}
}
private fun updateNotification(contentText: String) {
private fun updateNotification(payload: NotificationPayload) {
val manager = getSystemService(NotificationManager::class.java)
manager.notify(NOTIFICATION_ID, buildNotification(contentText))
manager.notify(NOTIFICATION_ID, buildNotification(payload))
}
private fun buildNotificationPayload(
config: NormalizedProxyConfig,
statusText: String,
): NotificationPayload {
val endpointText = getString(R.string.notification_endpoint, config.host, config.port)
val detailsText = getString(
R.string.notification_details,
config.host,
config.port,
config.dcIpList.size,
if (config.verbose) {
getString(R.string.notification_verbose_on)
} else {
getString(R.string.notification_verbose_off)
},
)
return NotificationPayload(
statusText = statusText,
endpointText = endpointText,
detailsText = detailsText,
)
}
private fun createOpenAppPendingIntent(): PendingIntent {
val launchIntent = packageManager.getLaunchIntentForPackage(packageName)
?.apply {
addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TOP or
Intent.FLAG_ACTIVITY_SINGLE_TOP,
)
}
?: Intent(this, MainActivity::class.java).apply {
addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TOP or
Intent.FLAG_ACTIVITY_SINGLE_TOP,
)
}
return TaskStackBuilder.create(this)
.addNextIntentWithParentStack(launchIntent)
.getPendingIntent(
1,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)
?: PendingIntent.getActivity(
this,
1,
launchIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)
}
private fun createStopPendingIntent(): PendingIntent {
val intent = Intent(this, ProxyForegroundService::class.java).apply {
action = ACTION_STOP
}
return PendingIntent.getService(
this,
2,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)
}
private fun createNotificationChannel() {
@ -149,3 +243,9 @@ class ProxyForegroundService : Service() {
}
}
}
private data class NotificationPayload(
val statusText: String,
val endpointText: String,
val detailsText: String,
)

View File

@ -35,6 +35,11 @@
<string name="notification_channel_description">Keeps the Telegram proxy service alive in the foreground.</string>
<string name="notification_starting">SOCKS5 %1$s:%2$d • starting embedded Python</string>
<string name="notification_running">SOCKS5 %1$s:%2$d • proxy active</string>
<string name="notification_endpoint">%1$s:%2$d</string>
<string name="notification_details">SOCKS5 endpoint: %1$s:%2$d\nDC mappings: %3$d\nVerbose logging: %4$s\nTap to open the app, or stop the service from this notification.</string>
<string name="notification_verbose_on">enabled</string>
<string name="notification_verbose_off">disabled</string>
<string name="notification_action_stop">Stop</string>
<string name="saved_config_invalid">Saved proxy settings are invalid.</string>
<string name="proxy_start_failed_generic">Failed to start embedded Python proxy.</string>
</resources>