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.Notification
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service import android.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import androidx.core.app.TaskStackBuilder
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -47,7 +49,14 @@ class ProxyForegroundService : Service() {
startForeground( startForeground(
NOTIFICATION_ID, NOTIFICATION_ID,
buildNotification( 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 { serviceScope.launch {
@ -68,11 +77,21 @@ class ProxyForegroundService : Service() {
override fun onBind(intent: Intent?): IBinder? = null 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) return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(getString(R.string.notification_title)) .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) .setSmallIcon(android.R.drawable.stat_sys_download_done)
.setContentIntent(createOpenAppPendingIntent())
.addAction(
0,
getString(R.string.notification_action_stop),
createStopPendingIntent(),
)
.setOngoing(true) .setOngoing(true)
.setOnlyAlertOnce(true) .setOnlyAlertOnce(true)
.build() .build()
@ -85,7 +104,16 @@ class ProxyForegroundService : Service() {
result.onSuccess { result.onSuccess {
ProxyServiceState.markStarted(config) 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 -> }.onFailure { error ->
ProxyServiceState.markFailed( ProxyServiceState.markFailed(
error.message ?: getString(R.string.proxy_start_failed_generic), 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) 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() { 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_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_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_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="saved_config_invalid">Saved proxy settings are invalid.</string>
<string name="proxy_start_failed_generic">Failed to start embedded Python proxy.</string> <string name="proxy_start_failed_generic">Failed to start embedded Python proxy.</string>
</resources> </resources>