|
@@ -1,7 +1,6 @@
|
|
|
package app.xixi.nomo
|
|
package app.xixi.nomo
|
|
|
|
|
|
|
|
import android.app.Notification
|
|
import android.app.Notification
|
|
|
-import android.app.NotificationManager
|
|
|
|
|
import android.content.BroadcastReceiver
|
|
import android.content.BroadcastReceiver
|
|
|
import android.content.Context
|
|
import android.content.Context
|
|
|
import android.content.Intent
|
|
import android.content.Intent
|
|
@@ -19,16 +18,18 @@ import app.xixi.nomo.XRayApi.Companion.VPN_STATE_CONNECTED
|
|
|
import app.xixi.nomo.XRayApi.Companion.VPN_STATE_ERROR
|
|
import app.xixi.nomo.XRayApi.Companion.VPN_STATE_ERROR
|
|
|
import app.xixi.nomo.XRayApi.Companion.VPN_STATE_IDLE
|
|
import app.xixi.nomo.XRayApi.Companion.VPN_STATE_IDLE
|
|
|
import com.google.gson.Gson
|
|
import com.google.gson.Gson
|
|
|
-import com.tekartik.sqflite.Constant
|
|
|
|
|
import go.Seq
|
|
import go.Seq
|
|
|
import ixvpn_mobile.Ixvpn_mobile
|
|
import ixvpn_mobile.Ixvpn_mobile
|
|
|
import ixvpn_mobile.ProxyConnectorHandler
|
|
import ixvpn_mobile.ProxyConnectorHandler
|
|
|
|
|
+import kotlinx.coroutines.Dispatchers
|
|
|
import kotlinx.coroutines.Job
|
|
import kotlinx.coroutines.Job
|
|
|
import kotlinx.coroutines.delay
|
|
import kotlinx.coroutines.delay
|
|
|
import kotlinx.coroutines.isActive
|
|
import kotlinx.coroutines.isActive
|
|
|
import kotlinx.coroutines.launch
|
|
import kotlinx.coroutines.launch
|
|
|
import kotlinx.coroutines.sync.Mutex
|
|
import kotlinx.coroutines.sync.Mutex
|
|
|
import kotlinx.coroutines.sync.withLock
|
|
import kotlinx.coroutines.sync.withLock
|
|
|
|
|
+import kotlinx.coroutines.withContext
|
|
|
|
|
+import kotlinx.coroutines.withTimeoutOrNull
|
|
|
import org.json.JSONObject
|
|
import org.json.JSONObject
|
|
|
import win.fkey.netboost.service.NetworkReporter
|
|
import win.fkey.netboost.service.NetworkReporter
|
|
|
|
|
|
|
@@ -38,27 +39,32 @@ class XRayService : LifecycleVpnService() {
|
|
|
private var tunFd: ParcelFileDescriptor? = null
|
|
private var tunFd: ParcelFileDescriptor? = null
|
|
|
private val mutex = Mutex()
|
|
private val mutex = Mutex()
|
|
|
|
|
|
|
|
|
|
+ @Volatile
|
|
|
private var vpnStatus = VPN_STATE_IDLE
|
|
private var vpnStatus = VPN_STATE_IDLE
|
|
|
|
|
|
|
|
// 计时功能相关变量
|
|
// 计时功能相关变量
|
|
|
private var timerJob: Job? = null
|
|
private var timerJob: Job? = null
|
|
|
- private var timerMode = TIMER_MODE_NORMAL // 0: 普通计时, 1: 倒计时
|
|
|
|
|
- private var timerBaseRealtime = 0L // 计时开始的真实时间(elapsedRealtime)
|
|
|
|
|
- private var timerInitialTime = 0L // 初始时间(对于倒计时是总时长,对于正常计时是0或已用时间)
|
|
|
|
|
- private var peekElapsedSeconds = 0L // Peek 上报计数器(秒)
|
|
|
|
|
|
|
+ private var timerMode = TIMER_MODE_NORMAL
|
|
|
|
|
+ private var timerBaseRealtime = 0L
|
|
|
|
|
+ private var timerInitialTime = 0L
|
|
|
|
|
+ private var peekElapsedSeconds = 0L
|
|
|
|
|
|
|
|
private var xrayConfig: XrayConfig? = null
|
|
private var xrayConfig: XrayConfig? = null
|
|
|
-
|
|
|
|
|
private var remainTime = 0L
|
|
private var remainTime = 0L
|
|
|
|
|
|
|
|
// 屏幕状态跟踪
|
|
// 屏幕状态跟踪
|
|
|
|
|
+ @Volatile
|
|
|
private var isScreenOn = true
|
|
private var isScreenOn = true
|
|
|
private var screenReceiver: BroadcastReceiver? = null
|
|
private var screenReceiver: BroadcastReceiver? = null
|
|
|
|
|
|
|
|
// 应用前后台状态跟踪
|
|
// 应用前后台状态跟踪
|
|
|
|
|
+ @Volatile
|
|
|
private var isAppForeground = true
|
|
private var isAppForeground = true
|
|
|
private var foregroundReceiver: BroadcastReceiver? = null
|
|
private var foregroundReceiver: BroadcastReceiver? = null
|
|
|
|
|
|
|
|
|
|
+ // 服务状态(实例级别)
|
|
|
|
|
+ private var serviceStatus: ServiceStatus = ServiceStatus.Disconnected
|
|
|
|
|
+
|
|
|
companion object {
|
|
companion object {
|
|
|
private val TAG: String = XRayService::class.java.simpleName
|
|
private val TAG: String = XRayService::class.java.simpleName
|
|
|
|
|
|
|
@@ -69,7 +75,15 @@ class XRayService : LifecycleVpnService() {
|
|
|
const val TIMER_MODE_NORMAL = 0 // 普通计时(正计时)
|
|
const val TIMER_MODE_NORMAL = 0 // 普通计时(正计时)
|
|
|
const val TIMER_MODE_COUNTDOWN = 1 // 倒计时
|
|
const val TIMER_MODE_COUNTDOWN = 1 // 倒计时
|
|
|
|
|
|
|
|
- private var serviceStatus: ServiceStatus = ServiceStatus.Disconnected
|
|
|
|
|
|
|
+ // VPN 网络配置常量
|
|
|
|
|
+ private const val TUN_IP_ADDRESS = "192.168.34.2"
|
|
|
|
|
+ private const val TUN_PREFIX_LENGTH = 24
|
|
|
|
|
+ private const val TUN_MTU = 1500
|
|
|
|
|
+ private const val DNS_SERVER = "8.8.8.8"
|
|
|
|
|
+ private const val VPN_SESSION_NAME = "ix_vpn"
|
|
|
|
|
+
|
|
|
|
|
+ // 超时常量
|
|
|
|
|
+ private const val STOP_TUNNEL_TIMEOUT_MS = 3000L
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
override fun onCreate() {
|
|
override fun onCreate() {
|
|
@@ -234,7 +248,6 @@ class XRayService : LifecycleVpnService() {
|
|
|
|
|
|
|
|
if (serviceStatus == ServiceStatus.Connected) {
|
|
if (serviceStatus == ServiceStatus.Connected) {
|
|
|
VLog.i(TAG, "VPN already connected")
|
|
VLog.i(TAG, "VPN already connected")
|
|
|
- stop()
|
|
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -266,54 +279,50 @@ class XRayService : LifecycleVpnService() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private fun startTun2Socks() {
|
|
private fun startTun2Socks() {
|
|
|
- VLog.i(TAG, "socksPort: ${xrayConfig?.socksPort}, sessionId: ${xrayConfig?.sessionId}")
|
|
|
|
|
- VLog.i(
|
|
|
|
|
- TAG,
|
|
|
|
|
- "allowVpnApps count: ${xrayConfig?.allowVpnApps?.size}, disallowVpnApps count: ${xrayConfig?.disallowVpnApps?.size}"
|
|
|
|
|
- )
|
|
|
|
|
- val builder = Builder()
|
|
|
|
|
- val ipAddress = "192.168.34.2"
|
|
|
|
|
- builder.setSession("ix_vpn")
|
|
|
|
|
- .setMtu(1500)
|
|
|
|
|
- .addAddress(ipAddress, 24)
|
|
|
|
|
- .addRoute("0.0.0.0", 0)
|
|
|
|
|
- .addDnsServer("8.8.8.8")
|
|
|
|
|
-
|
|
|
|
|
- if (xrayConfig?.allowVpnApps!!.isNotEmpty()) {
|
|
|
|
|
- xrayConfig?.allowVpnApps!!.forEach { app ->
|
|
|
|
|
|
|
+ val config = xrayConfig ?: run {
|
|
|
|
|
+ VLog.e(TAG, "xrayConfig is null")
|
|
|
|
|
+ sendStatusMessage(VPN_STATE_ERROR, ERROR_INIT, "Config is null")
|
|
|
|
|
+ stopSelfService()
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ VLog.i(TAG, "socksPort: ${config.socksPort}, sessionId: ${config.sessionId}")
|
|
|
|
|
+ VLog.i(TAG, "allowVpnApps count: ${config.allowVpnApps.size}, disallowVpnApps count: ${config.disallowVpnApps.size}")
|
|
|
|
|
+
|
|
|
|
|
+ val builder = Builder().apply {
|
|
|
|
|
+ setSession(VPN_SESSION_NAME)
|
|
|
|
|
+ setMtu(TUN_MTU)
|
|
|
|
|
+ addAddress(TUN_IP_ADDRESS, TUN_PREFIX_LENGTH)
|
|
|
|
|
+ addRoute("0.0.0.0", 0)
|
|
|
|
|
+ addDnsServer(DNS_SERVER)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 配置应用白名单/黑名单
|
|
|
|
|
+ if (config.allowVpnApps.isNotEmpty()) {
|
|
|
|
|
+ config.allowVpnApps.forEach { app ->
|
|
|
builder.addAllowedApplication(app)
|
|
builder.addAllowedApplication(app)
|
|
|
}
|
|
}
|
|
|
- VLog.i(TAG, "允许的应用: ${xrayConfig?.allowVpnApps?.joinToString(", ")}")
|
|
|
|
|
|
|
+ VLog.i(TAG, "允许的应用: ${config.allowVpnApps.joinToString(", ")}")
|
|
|
} else {
|
|
} else {
|
|
|
- xrayConfig?.disallowVpnApps!!.forEach { app ->
|
|
|
|
|
|
|
+ config.disallowVpnApps.forEach { app ->
|
|
|
builder.addDisallowedApplication(app)
|
|
builder.addDisallowedApplication(app)
|
|
|
}
|
|
}
|
|
|
builder.addDisallowedApplication(packageName)
|
|
builder.addDisallowedApplication(packageName)
|
|
|
- VLog.i(TAG, "禁止的应用: ${xrayConfig?.disallowVpnApps?.joinToString(", ")}")
|
|
|
|
|
|
|
+ VLog.i(TAG, "禁止的应用: ${config.disallowVpnApps.joinToString(", ")}")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 初始化网络请求参数
|
|
// 初始化网络请求参数
|
|
|
- NetworkReporter.getInstance().initialize(xrayConfig!!)
|
|
|
|
|
|
|
+ NetworkReporter.getInstance().initialize(config)
|
|
|
|
|
|
|
|
tunFd = builder.establish()
|
|
tunFd = builder.establish()
|
|
|
tunFd?.let { tfd ->
|
|
tunFd?.let { tfd ->
|
|
|
VLog.i(TAG, "VPN tunnel established, fd: ${tfd.fd}")
|
|
VLog.i(TAG, "VPN tunnel established, fd: ${tfd.fd}")
|
|
|
- tunnelService.startTunnel(
|
|
|
|
|
- this,
|
|
|
|
|
- xrayConfig!!.socksPort,
|
|
|
|
|
- xrayConfig!!.tunnelConfig,
|
|
|
|
|
- tfd.fd
|
|
|
|
|
- )
|
|
|
|
|
- Ixvpn_mobile.proxyConnectorStart(
|
|
|
|
|
- xrayConfig!!.sessionId,
|
|
|
|
|
- xrayConfig!!.startOptions,
|
|
|
|
|
- vpnHandler
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ tunnelService.startTunnel(this, config.socksPort, config.tunnelConfig, tfd.fd)
|
|
|
|
|
+ Ixvpn_mobile.proxyConnectorStart(config.sessionId, config.startOptions, vpnHandler)
|
|
|
VLog.i(TAG, "XRay proxy started successfully")
|
|
VLog.i(TAG, "XRay proxy started successfully")
|
|
|
} ?: run {
|
|
} ?: run {
|
|
|
VLog.e(TAG, "Failed to establish VPN tunnel")
|
|
VLog.e(TAG, "Failed to establish VPN tunnel")
|
|
|
updateStatus(ServiceStatus.Failed)
|
|
updateStatus(ServiceStatus.Failed)
|
|
|
- // 通知 Flutter 层启动失败
|
|
|
|
|
sendStatusMessage(VPN_STATE_ERROR, ERROR_INIT, "Failed to establish VPN tunnel")
|
|
sendStatusMessage(VPN_STATE_ERROR, ERROR_INIT, "Failed to establish VPN tunnel")
|
|
|
stopSelfService()
|
|
stopSelfService()
|
|
|
}
|
|
}
|
|
@@ -326,64 +335,78 @@ class XRayService : LifecycleVpnService() {
|
|
|
}
|
|
}
|
|
|
vpnStatus = status
|
|
vpnStatus = status
|
|
|
|
|
|
|
|
- var code = 0L
|
|
|
|
|
- var message = ""
|
|
|
|
|
- try {
|
|
|
|
|
- val json = JSONObject(params)
|
|
|
|
|
- code = json.optLong("code", 0)
|
|
|
|
|
- message = json.optString("message", "")
|
|
|
|
|
- } catch (e: Exception) {
|
|
|
|
|
- VLog.e(TAG, "解析 params JSON 失败", e)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ val (code, message) = parseStatusParams(params)
|
|
|
|
|
|
|
|
- // VPN 连接成功后启动计时
|
|
|
|
|
- if (status == VPN_STATE_CONNECTED) {
|
|
|
|
|
- val mode = if (xrayConfig!!.isCountdown) TIMER_MODE_COUNTDOWN else TIMER_MODE_NORMAL
|
|
|
|
|
- val time = if (xrayConfig!!.isCountdown) remainTime else 0L
|
|
|
|
|
- startTimer(mode, time)
|
|
|
|
|
- uploadBoostResult(true, code)
|
|
|
|
|
- AppLogger.getInstance(this).updateAppJsonStatusInfo(true, code)
|
|
|
|
|
- } else if (status == VPN_STATE_ERROR) {
|
|
|
|
|
- AppLogger.getInstance(this).updateAppJsonStatusInfo(false, code)
|
|
|
|
|
- uploadBoostResult(false, code)
|
|
|
|
|
- VLog.i(TAG, "vpnHandler")
|
|
|
|
|
- lifecycleScope.launch { stop() }
|
|
|
|
|
|
|
+ when (status) {
|
|
|
|
|
+ VPN_STATE_CONNECTED -> {
|
|
|
|
|
+ val config = xrayConfig ?: return@ProxyConnectorHandler
|
|
|
|
|
+ val mode = if (config.isCountdown) TIMER_MODE_COUNTDOWN else TIMER_MODE_NORMAL
|
|
|
|
|
+ val time = if (config.isCountdown) remainTime else 0L
|
|
|
|
|
+ startTimer(mode, time)
|
|
|
|
|
+ uploadBoostResult(true, code)
|
|
|
|
|
+ AppLogger.getInstance(this).updateAppJsonStatusInfo(true, code)
|
|
|
|
|
+ }
|
|
|
|
|
+ VPN_STATE_ERROR -> {
|
|
|
|
|
+ AppLogger.getInstance(this).updateAppJsonStatusInfo(false, code)
|
|
|
|
|
+ uploadBoostResult(false, code)
|
|
|
|
|
+ VLog.i(TAG, "vpnHandler error")
|
|
|
|
|
+ lifecycleScope.launch { stop() }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- // 发送状态消息到XRayApi
|
|
|
|
|
|
|
|
|
|
sendStatusMessage(status, code, message)
|
|
sendStatusMessage(status, code, message)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private fun parseStatusParams(params: String): Pair<Long, String> {
|
|
|
|
|
+ return try {
|
|
|
|
|
+ val json = JSONObject(params)
|
|
|
|
|
+ Pair(json.optLong("code", 0), json.optString("message", ""))
|
|
|
|
|
+ } catch (e: Exception) {
|
|
|
|
|
+ VLog.e(TAG, "解析 params JSON 失败", e)
|
|
|
|
|
+ Pair(0L, "")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private fun uploadBoostResult(success: Boolean, code: Long) {
|
|
private fun uploadBoostResult(success: Boolean, code: Long) {
|
|
|
|
|
+ val config = xrayConfig ?: return
|
|
|
try {
|
|
try {
|
|
|
- // 更新 实时流量
|
|
|
|
|
- val queryTypes = byteArrayOf(1)
|
|
|
|
|
- val stats: String? = Ixvpn_mobile.proxyConnectorQueryStats(queryTypes)
|
|
|
|
|
- val session: String = xrayConfig!!.sessionId
|
|
|
|
|
- NetworkReporter.getInstance().addBoostResultParams(
|
|
|
|
|
|
|
+ val stats = queryStats(1) // QueryStatsConnectionHistory
|
|
|
|
|
+ val reporter = NetworkReporter.getInstance()
|
|
|
|
|
+
|
|
|
|
|
+ reporter.addBoostResultParams(
|
|
|
"NM_BoostResult",
|
|
"NM_BoostResult",
|
|
|
stats,
|
|
stats,
|
|
|
- session,
|
|
|
|
|
|
|
+ config.sessionId,
|
|
|
success,
|
|
success,
|
|
|
code
|
|
code
|
|
|
)
|
|
)
|
|
|
- val param = NetworkReporter.getInstance()
|
|
|
|
|
- .buildRequestParams("NM_BoostResult")
|
|
|
|
|
- val intent = Intent(
|
|
|
|
|
- BOOST_RESULT_BROADCAST
|
|
|
|
|
- ).apply {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ val intent = Intent(BOOST_RESULT_BROADCAST).apply {
|
|
|
setPackage(packageName)
|
|
setPackage(packageName)
|
|
|
- putExtra("param", param)
|
|
|
|
|
|
|
+ putExtra("param", reporter.buildRequestParams("NM_BoostResult"))
|
|
|
putExtra("success", success)
|
|
putExtra("success", success)
|
|
|
- putExtra("locationCode", NetworkReporter.getInstance().getLocationCode())
|
|
|
|
|
- putExtra("nodeId", NetworkReporter.getInstance().getConnectedNodeId())
|
|
|
|
|
|
|
+ putExtra("locationCode", reporter.getLocationCode())
|
|
|
|
|
+ putExtra("nodeId", reporter.getConnectedNodeId())
|
|
|
}
|
|
}
|
|
|
sendBroadcast(intent)
|
|
sendBroadcast(intent)
|
|
|
- } catch (e: java.lang.Exception) {
|
|
|
|
|
|
|
+ } catch (e: Exception) {
|
|
|
VLog.e(TAG, "uploadBoostResult error: ${e.message}")
|
|
VLog.e(TAG, "uploadBoostResult error: ${e.message}")
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 查询代理连接统计数据
|
|
|
|
|
+ * @param types 查询类型: 1=连接历史, 2=带宽, 3=最大速度
|
|
|
|
|
+ */
|
|
|
|
|
+ private fun queryStats(vararg types: Byte): String? {
|
|
|
|
|
+ return try {
|
|
|
|
|
+ Ixvpn_mobile.proxyConnectorQueryStats(types)
|
|
|
|
|
+ } catch (e: Exception) {
|
|
|
|
|
+ VLog.e(TAG, "queryStats error: ${e.message}")
|
|
|
|
|
+ null
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private fun sendStatusMessage(status: Long, code: Long, message: String) {
|
|
private fun sendStatusMessage(status: Long, code: Long, message: String) {
|
|
|
VLog.i(TAG, "sendStatusMessage status:$status code:$code message:$message")
|
|
VLog.i(TAG, "sendStatusMessage status:$status code:$code message:$message")
|
|
|
val intent = Intent(STATUS_BROADCAST).apply {
|
|
val intent = Intent(STATUS_BROADCAST).apply {
|
|
@@ -423,44 +446,56 @@ class XRayService : LifecycleVpnService() {
|
|
|
stopSelf()
|
|
stopSelf()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private fun stopTun2Socks() {
|
|
|
|
|
|
|
+ private suspend fun stopTun2Socks() {
|
|
|
|
|
+ VLog.i(TAG, "stopTun2Socks")
|
|
|
// 停止计时
|
|
// 停止计时
|
|
|
stopTimer()
|
|
stopTimer()
|
|
|
|
|
|
|
|
dealStat()
|
|
dealStat()
|
|
|
|
|
|
|
|
- // 注意:必须先停止 tunnel,再关闭 fd
|
|
|
|
|
- // 否则 hev-socks5-tunnel 可能在读写 fd 时卡住
|
|
|
|
|
- try {
|
|
|
|
|
- // 使用单独线程执行 stopTunnel,避免阻塞,并添加超时
|
|
|
|
|
- val stopThread = Thread {
|
|
|
|
|
|
|
+ // 停止顺序优化:
|
|
|
|
|
+ // 1. 先停止 proxy connector,切断数据源
|
|
|
|
|
+ // 2. 再停止 tunnel(此时不再有新数据进入)
|
|
|
|
|
+ // 3. 最后关闭 tun fd
|
|
|
|
|
+ stopProxyConnector()
|
|
|
|
|
+ stopTunnelWithTimeout()
|
|
|
|
|
+ closeTunFd()
|
|
|
|
|
+
|
|
|
|
|
+ VLog.i(TAG, "xray stopped")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private suspend fun stopTunnelWithTimeout() {
|
|
|
|
|
+ val startTime = System.currentTimeMillis()
|
|
|
|
|
+ val result = withTimeoutOrNull(STOP_TUNNEL_TIMEOUT_MS) {
|
|
|
|
|
+ withContext(Dispatchers.IO) {
|
|
|
try {
|
|
try {
|
|
|
tunnelService.stopTunnel()
|
|
tunnelService.stopTunnel()
|
|
|
- VLog.i(TAG, "Tunnel stopped")
|
|
|
|
|
|
|
+ val elapsed = System.currentTimeMillis() - startTime
|
|
|
|
|
+ VLog.i(TAG, "Tunnel stopped in ${elapsed}ms")
|
|
|
|
|
+ true
|
|
|
} catch (e: Exception) {
|
|
} catch (e: Exception) {
|
|
|
VLog.e(TAG, "停止 tunnel 失败", e)
|
|
VLog.e(TAG, "停止 tunnel 失败", e)
|
|
|
|
|
+ false
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- stopThread.start()
|
|
|
|
|
- // 等待最多 3 秒
|
|
|
|
|
- stopThread.join(3000)
|
|
|
|
|
- if (stopThread.isAlive) {
|
|
|
|
|
- VLog.w(TAG, "stopTunnel 超时,强制继续")
|
|
|
|
|
- stopThread.interrupt()
|
|
|
|
|
- }
|
|
|
|
|
- } catch (e: Exception) {
|
|
|
|
|
- VLog.e(TAG, "停止 tunnel 异常", e)
|
|
|
|
|
}
|
|
}
|
|
|
|
|
+ if (result == null) {
|
|
|
|
|
+ val elapsed = System.currentTimeMillis() - startTime
|
|
|
|
|
+ VLog.w(TAG, "stopTunnel 超时 (${elapsed}ms),强制继续")
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+ private fun stopProxyConnector() {
|
|
|
try {
|
|
try {
|
|
|
Ixvpn_mobile.proxyConnectorStop()
|
|
Ixvpn_mobile.proxyConnectorStop()
|
|
|
VLog.i(TAG, "Proxy connector stopped")
|
|
VLog.i(TAG, "Proxy connector stopped")
|
|
|
} catch (e: Exception) {
|
|
} catch (e: Exception) {
|
|
|
VLog.e(TAG, "停止 proxy connector 失败", e)
|
|
VLog.e(TAG, "停止 proxy connector 失败", e)
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 关闭 TUN fd
|
|
|
|
|
- tunFd?.let { tfd ->
|
|
|
|
|
|
|
+ private fun closeTunFd() {
|
|
|
|
|
+ tunFd?.let { tfd ->
|
|
|
try {
|
|
try {
|
|
|
tfd.close()
|
|
tfd.close()
|
|
|
VLog.i(TAG, "TUN file descriptor closed")
|
|
VLog.i(TAG, "TUN file descriptor closed")
|
|
@@ -469,7 +504,6 @@ class XRayService : LifecycleVpnService() {
|
|
|
}
|
|
}
|
|
|
tunFd = null
|
|
tunFd = null
|
|
|
}
|
|
}
|
|
|
- VLog.i(TAG, "xray stopped")
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -524,25 +558,17 @@ class XRayService : LifecycleVpnService() {
|
|
|
// 检查倒计时是否结束
|
|
// 检查倒计时是否结束
|
|
|
if (timerMode == TIMER_MODE_COUNTDOWN && currentTime <= 0) {
|
|
if (timerMode == TIMER_MODE_COUNTDOWN && currentTime <= 0) {
|
|
|
VLog.i(TAG, "倒计时结束 - 关闭VPN (elapsed: ${elapsedTime}ms)")
|
|
VLog.i(TAG, "倒计时结束 - 关闭VPN (elapsed: ${elapsedTime}ms)")
|
|
|
-// sendTimerUpdate(0L)
|
|
|
|
|
stop()
|
|
stop()
|
|
|
sendStatusMessage(VPN_STATE_ERROR, ERROR_REMAIN_TIME, "No available time")
|
|
sendStatusMessage(VPN_STATE_ERROR, ERROR_REMAIN_TIME, "No available time")
|
|
|
return@launch
|
|
return@launch
|
|
|
}
|
|
}
|
|
|
if (timerMode == TIMER_MODE_NORMAL && currentTime >= remainTime) {
|
|
if (timerMode == TIMER_MODE_NORMAL && currentTime >= remainTime) {
|
|
|
VLog.i(TAG, "可用时间已结束 - 关闭VPN (elapsed: ${elapsedTime}ms)")
|
|
VLog.i(TAG, "可用时间已结束 - 关闭VPN (elapsed: ${elapsedTime}ms)")
|
|
|
-// sendTimerUpdate(0L)
|
|
|
|
|
stop()
|
|
stop()
|
|
|
sendStatusMessage(VPN_STATE_ERROR, ERROR_REMAIN_TIME, "No available time")
|
|
sendStatusMessage(VPN_STATE_ERROR, ERROR_REMAIN_TIME, "No available time")
|
|
|
return@launch
|
|
return@launch
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 更新通知
|
|
|
|
|
-// updateTimerNotification(currentTime)
|
|
|
|
|
-
|
|
|
|
|
- // 发送计时更新广播
|
|
|
|
|
-// sendTimerUpdate(currentTime)
|
|
|
|
|
-
|
|
|
|
|
// Peek 定时上报(屏幕关闭时不上报,节省资源)
|
|
// Peek 定时上报(屏幕关闭时不上报,节省资源)
|
|
|
peekElapsedSeconds++
|
|
peekElapsedSeconds++
|
|
|
if (peekInterval > 0 && peekElapsedSeconds >= peekInterval) {
|
|
if (peekInterval > 0 && peekElapsedSeconds >= peekInterval) {
|
|
@@ -571,51 +597,14 @@ class XRayService : LifecycleVpnService() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 发送计时更新广播
|
|
|
|
|
- * 屏幕关闭时不发送,节省资源
|
|
|
|
|
- */
|
|
|
|
|
-// private fun sendTimerUpdate(currentTime: Long) {
|
|
|
|
|
-// // 屏幕关闭或应用在后台时不发送广播,节省资源
|
|
|
|
|
-// if (!isScreenOn || !isAppForeground) {
|
|
|
|
|
-// return
|
|
|
|
|
-// }
|
|
|
|
|
-// val intent = Intent(TIMER_BROADCAST).apply {
|
|
|
|
|
-// setPackage(packageName) // 必须设置包名,否则 RECEIVER_NOT_EXPORTED 接收不到
|
|
|
|
|
-// putExtra("currentTime", currentTime)
|
|
|
|
|
-// putExtra("mode", timerMode)
|
|
|
|
|
-// }
|
|
|
|
|
-// sendBroadcast(intent)
|
|
|
|
|
-// }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 更新通知显示计时
|
|
|
|
|
- */
|
|
|
|
|
- private fun updateTimerNotification(currentTime: Long) {
|
|
|
|
|
- val timeText = formatTime(currentTime)
|
|
|
|
|
- val notification = createConnectionNotification(
|
|
|
|
|
- this,
|
|
|
|
|
- NOTIFICATION_CHANNEL_ID,
|
|
|
|
|
- "NOMO VPN",
|
|
|
|
|
- "Running - $timeText",
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- val notificationManager = getSystemService(NotificationManager::class.java)
|
|
|
|
|
- notificationManager?.notify(FOREGROUND_SERVICE_ID, notification)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 查询日志状态
|
|
|
|
|
- * QueryStatsConnectionHistory = 1 查询连接历史
|
|
|
|
|
- * QueryStatsBandwidth = 2 带宽
|
|
|
|
|
- * QueryStatsMaxSpeed = 3 最大速度
|
|
|
|
|
|
|
+ * 查询并记录统计日志
|
|
|
*/
|
|
*/
|
|
|
private fun dealStat() {
|
|
private fun dealStat() {
|
|
|
- val queryTypes = byteArrayOf(1, 2, 3)
|
|
|
|
|
- val stats: String? = Ixvpn_mobile.proxyConnectorQueryStats(queryTypes)
|
|
|
|
|
|
|
+ val stats = queryStats(1, 2, 3) // 连接历史、带宽、最大速度
|
|
|
val version = Ixvpn_mobile.proxyConnectorVersion()
|
|
val version = Ixvpn_mobile.proxyConnectorVersion()
|
|
|
- VLog.i(Constant.TAG, "stats = $stats")
|
|
|
|
|
-
|
|
|
|
|
- // 将stats和version写入core.json日志文件
|
|
|
|
|
|
|
+ VLog.i(TAG, "stats = $stats")
|
|
|
|
|
+
|
|
|
|
|
+ // 将 stats 和 version 写入 core.json 日志文件
|
|
|
CoreLogger.getInstance(this).updateStatsAndVersion(stats, version)
|
|
CoreLogger.getInstance(this).updateStatsAndVersion(stats, version)
|
|
|
// 更新停止时间
|
|
// 更新停止时间
|
|
|
AppLogger.getInstance(this).updateAppJsonStopTime()
|
|
AppLogger.getInstance(this).updateAppJsonStopTime()
|
|
@@ -625,27 +614,14 @@ class XRayService : LifecycleVpnService() {
|
|
|
* 调用 Peek 接口上报数据
|
|
* 调用 Peek 接口上报数据
|
|
|
*/
|
|
*/
|
|
|
private fun callPeekInterface() {
|
|
private fun callPeekInterface() {
|
|
|
|
|
+ val config = xrayConfig ?: return
|
|
|
try {
|
|
try {
|
|
|
- // 获取实时流量数据
|
|
|
|
|
- // 更新 实时流量
|
|
|
|
|
- val queryTypes = byteArrayOf(3)
|
|
|
|
|
- val stats: String? = Ixvpn_mobile.proxyConnectorQueryStats(queryTypes)
|
|
|
|
|
- VLog.i(TAG, "peek stats : $stats")
|
|
|
|
|
-
|
|
|
|
|
- // 设置 Peek Log 参数
|
|
|
|
|
- NetworkReporter.getInstance().addPeekLogParams(
|
|
|
|
|
- "NM_PeekLog",
|
|
|
|
|
- stats,
|
|
|
|
|
- xrayConfig!!.sessionId
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ val stats = queryStats(3) // QueryStatsMaxSpeed
|
|
|
|
|
+ VLog.i(TAG, "peek stats: $stats")
|
|
|
|
|
|
|
|
- // 上报数据
|
|
|
|
|
- NetworkReporter.getInstance().report(
|
|
|
|
|
- "/api/v1/event",
|
|
|
|
|
- "NM_PeekLog",
|
|
|
|
|
- null,
|
|
|
|
|
- null
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ val reporter = NetworkReporter.getInstance()
|
|
|
|
|
+ reporter.addPeekLogParams("NM_PeekLog", stats, config.sessionId)
|
|
|
|
|
+ reporter.report("/api/v1/event", "NM_PeekLog", null, null)
|
|
|
|
|
|
|
|
VLog.i(TAG, "Peek log reported successfully")
|
|
VLog.i(TAG, "Peek log reported successfully")
|
|
|
} catch (e: Exception) {
|
|
} catch (e: Exception) {
|