feat(android): add richer service notification
This commit is contained in:
parent
db5a6cc696
commit
8d43fa25fa
|
|
@ -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,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue