Update v1.0.4

This commit is contained in:
amurcanov
2026-03-29 02:09:50 +03:00
parent 70e724dfcf
commit c7dd445c2e
8 changed files with 248 additions and 850 deletions
@@ -0,0 +1,196 @@
package com.amurcanov.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.content.pm.ServiceInfo
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
import androidx.core.app.NotificationCompat
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class ProxyService : Service() {
private var wakeLock: PowerManager.WakeLock? = null
private var statsJob: Job? = null
companion object {
const val ACTION_START = "com.amurcanov.tgwsproxy.START"
const val ACTION_STOP = "com.amurcanov.tgwsproxy.STOP"
const val EXTRA_PORT = "EXTRA_PORT"
const val EXTRA_IPS = "EXTRA_IPS"
const val EXTRA_POOL_SIZE = "EXTRA_POOL_SIZE"
private const val NOTIFICATION_ID = 1
private const val CHANNEL_ID = "ProxyServiceChannel"
private val _isRunning = MutableStateFlow(false)
val isRunning: StateFlow<Boolean> = _isRunning
}
override fun onCreate() {
super.onCreate()
createNotificationChannel()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
ACTION_START -> {
val port = intent.getIntExtra(EXTRA_PORT, 8080)
val ips = intent.getStringExtra(EXTRA_IPS) ?: ""
val poolSize = intent.getIntExtra(EXTRA_POOL_SIZE, 4)
startProxy(port, ips, poolSize)
}
ACTION_STOP -> {
stopProxy()
}
}
return START_STICKY
}
private fun startProxy(port: Int, ips: String, poolSize: Int = 4) {
if (_isRunning.value) return
val notification = createNotification("Запуск прокси...")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
} else {
startForeground(NOTIFICATION_ID, notification)
}
acquireWakeLock()
Thread {
NativeProxy.setPoolSize(poolSize)
NativeProxy.startProxy("127.0.0.1", port, ips, 1)
}.start()
_isRunning.value = true
statsJob = CoroutineScope(Dispatchers.IO).launch {
while (isActive) {
delay(2000)
if (_isRunning.value) {
val rawStats = NativeProxy.getStats() ?: continue
val upRaw = extractStat(rawStats, "up=")
val downRaw = extractStat(rawStats, "down=")
val totalBytes = parseHumanBytes(upRaw) + parseHumanBytes(downRaw)
val text = "Трафик: ${formatBytes(totalBytes)}"
val manager = getSystemService(NotificationManager::class.java)
manager?.notify(NOTIFICATION_ID, createNotification(text))
}
}
}
}
private fun extractStat(stats: String, key: String): String {
val idx = stats.indexOf(key)
if (idx == -1) return "0B"
val start = idx + key.length
val end = stats.indexOf(" ", start)
return if (end == -1) stats.substring(start) else stats.substring(start, end)
}
private fun parseHumanBytes(s: String): Double {
val num = s.replace(Regex("[^0-9.]"), "").toDoubleOrNull() ?: 0.0
return when {
s.endsWith("TB") -> num * 1024.0 * 1024 * 1024 * 1024
s.endsWith("GB") -> num * 1024.0 * 1024 * 1024
s.endsWith("MB") -> num * 1024.0 * 1024
s.endsWith("KB") -> num * 1024.0
else -> num
}
}
private fun formatBytes(bytes: Double): String {
if (bytes < 1024) return "%.0fB".format(bytes)
if (bytes < 1024 * 1024) return "%.1fKB".format(bytes / 1024)
if (bytes < 1024 * 1024 * 1024) return "%.1fMB".format(bytes / (1024 * 1024))
return "%.2fGB".format(bytes / (1024 * 1024 * 1024))
}
private fun stopProxy() {
statsJob?.cancel()
statsJob = null
Thread { NativeProxy.stopProxy() }.start()
releaseWakeLock()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
stopForeground(STOP_FOREGROUND_REMOVE)
} else {
stopForeground(true)
}
stopSelf()
_isRunning.value = false
}
private fun acquireWakeLock() {
val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
wakeLock = powerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
"TgWsProxy::ServiceWakeLock"
)
wakeLock?.acquire()
}
private fun releaseWakeLock() {
try {
wakeLock?.let {
if (it.isHeld) {
it.release()
}
}
} catch (e: Exception) {
// Ignore wakelock exception
}
wakeLock = null
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(
CHANNEL_ID,
"Фоновый Прокси",
NotificationManager.IMPORTANCE_LOW
)
val manager = getSystemService(NotificationManager::class.java)
manager?.createNotificationChannel(serviceChannel)
}
}
private fun createNotification(content: String): Notification {
val stopIntent = Intent(this, ProxyService::class.java).apply {
action = ACTION_STOP
}
val stopPendingIntent = PendingIntent.getService(
this, 0, stopIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Telegram WS Proxy")
.setContentText(content)
.setSmallIcon(R.drawable.ic_notification) // Local pure vector for Android 16 compatibility
.addAction(android.R.drawable.ic_menu_close_clear_cancel, "Отключить", stopPendingIntent)
.setOngoing(true)
.setOnlyAlertOnce(true) // prevent vibrate/sound on updates
.build()
}
override fun onDestroy() {
if (_isRunning.value) {
stopProxy()
}
super.onDestroy()
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
}