|
@@ -45,8 +45,7 @@ class XRayService : LifecycleVpnService() {
|
|
|
private var timerMode = TIMER_MODE_NORMAL // 0: 普通计时, 1: 倒计时
|
|
private var timerMode = TIMER_MODE_NORMAL // 0: 普通计时, 1: 倒计时
|
|
|
private var timerBaseRealtime = 0L // 计时开始的真实时间(elapsedRealtime)
|
|
private var timerBaseRealtime = 0L // 计时开始的真实时间(elapsedRealtime)
|
|
|
private var timerInitialTime = 0L // 初始时间(对于倒计时是总时长,对于正常计时是0或已用时间)
|
|
private var timerInitialTime = 0L // 初始时间(对于倒计时是总时长,对于正常计时是0或已用时间)
|
|
|
- private var timerPausedElapsed = 0L // 暂停时已经过的时间
|
|
|
|
|
- private var isTimerPaused = false
|
|
|
|
|
|
|
+ private var peekElapsedSeconds = 0L // Peek 上报计数器(秒)
|
|
|
|
|
|
|
|
private var xrayConfig: XrayConfig? = null
|
|
private var xrayConfig: XrayConfig? = null
|
|
|
|
|
|
|
@@ -241,8 +240,9 @@ class XRayService : LifecycleVpnService() {
|
|
|
val configJson = bundle.getString("config")
|
|
val configJson = bundle.getString("config")
|
|
|
xrayConfig = Gson().fromJson(configJson, XrayConfig::class.java)
|
|
xrayConfig = Gson().fromJson(configJson, XrayConfig::class.java)
|
|
|
|
|
|
|
|
- if(xrayConfig == null) {
|
|
|
|
|
|
|
+ if (xrayConfig == null) {
|
|
|
updateStatus(ServiceStatus.Failed)
|
|
updateStatus(ServiceStatus.Failed)
|
|
|
|
|
+ // 未启动tun,通知flutter并且调用stopSelf关闭服务
|
|
|
sendStatusMessage(VPN_STATE_ERROR, ERROR_INIT, "参数异常")
|
|
sendStatusMessage(VPN_STATE_ERROR, ERROR_INIT, "参数异常")
|
|
|
stopSelfService()
|
|
stopSelfService()
|
|
|
return
|
|
return
|
|
@@ -297,8 +297,17 @@ class XRayService : LifecycleVpnService() {
|
|
|
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,
|
|
|
|
|
+ xrayConfig!!.socksPort,
|
|
|
|
|
+ xrayConfig!!.tunnelConfig,
|
|
|
|
|
+ tfd.fd
|
|
|
|
|
+ )
|
|
|
|
|
+ Ixvpn_mobile.proxyConnectorStart(
|
|
|
|
|
+ xrayConfig!!.sessionId,
|
|
|
|
|
+ xrayConfig!!.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")
|
|
@@ -333,7 +342,6 @@ class XRayService : LifecycleVpnService() {
|
|
|
startTimer(mode, time)
|
|
startTimer(mode, time)
|
|
|
uploadBoostResult(true, code)
|
|
uploadBoostResult(true, code)
|
|
|
AppLogger.getInstance(this).updateAppJsonStatusInfo(true, code)
|
|
AppLogger.getInstance(this).updateAppJsonStatusInfo(true, code)
|
|
|
- dealStat()
|
|
|
|
|
} else if (status == VPN_STATE_ERROR) {
|
|
} else if (status == VPN_STATE_ERROR) {
|
|
|
AppLogger.getInstance(this).updateAppJsonStatusInfo(false, code)
|
|
AppLogger.getInstance(this).updateAppJsonStatusInfo(false, code)
|
|
|
uploadBoostResult(false, code)
|
|
uploadBoostResult(false, code)
|
|
@@ -366,7 +374,7 @@ class XRayService : LifecycleVpnService() {
|
|
|
setPackage(packageName)
|
|
setPackage(packageName)
|
|
|
putExtra("param", param)
|
|
putExtra("param", param)
|
|
|
putExtra("success", success)
|
|
putExtra("success", success)
|
|
|
- putExtra("locationCode", NetworkReporter.getInstance().getLocationCode())
|
|
|
|
|
|
|
+ putExtra("locationCode", NetworkReporter.getInstance().getLocationCode())
|
|
|
putExtra("nodeId", NetworkReporter.getInstance().getConnectedNodeId())
|
|
putExtra("nodeId", NetworkReporter.getInstance().getConnectedNodeId())
|
|
|
}
|
|
}
|
|
|
sendBroadcast(intent)
|
|
sendBroadcast(intent)
|
|
@@ -418,22 +426,29 @@ class XRayService : LifecycleVpnService() {
|
|
|
// 停止计时
|
|
// 停止计时
|
|
|
stopTimer()
|
|
stopTimer()
|
|
|
|
|
|
|
|
- tunFd?.let { tfd ->
|
|
|
|
|
- try {
|
|
|
|
|
- tfd.close()
|
|
|
|
|
- VLog.i(TAG, "TUN file descriptor closed")
|
|
|
|
|
- } catch (e: Exception) {
|
|
|
|
|
- VLog.e(TAG, "关闭 TUN file descriptor 失败", e)
|
|
|
|
|
- }
|
|
|
|
|
- tunFd = null
|
|
|
|
|
- }
|
|
|
|
|
- VLog.i(TAG, "xray stopped")
|
|
|
|
|
|
|
+ dealStat()
|
|
|
|
|
|
|
|
|
|
+ // 注意:必须先停止 tunnel,再关闭 fd
|
|
|
|
|
+ // 否则 hev-socks5-tunnel 可能在读写 fd 时卡住
|
|
|
try {
|
|
try {
|
|
|
- tunnelService.stopTunnel()
|
|
|
|
|
- VLog.i(TAG, "Tunnel stopped")
|
|
|
|
|
|
|
+ // 使用单独线程执行 stopTunnel,避免阻塞,并添加超时
|
|
|
|
|
+ val stopThread = Thread {
|
|
|
|
|
+ try {
|
|
|
|
|
+ tunnelService.stopTunnel()
|
|
|
|
|
+ VLog.i(TAG, "Tunnel stopped")
|
|
|
|
|
+ } catch (e: Exception) {
|
|
|
|
|
+ VLog.e(TAG, "停止 tunnel 失败", e)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ stopThread.start()
|
|
|
|
|
+ // 等待最多 3 秒
|
|
|
|
|
+ stopThread.join(3000)
|
|
|
|
|
+ if (stopThread.isAlive) {
|
|
|
|
|
+ VLog.w(TAG, "stopTunnel 超时,强制继续")
|
|
|
|
|
+ stopThread.interrupt()
|
|
|
|
|
+ }
|
|
|
} catch (e: Exception) {
|
|
} catch (e: Exception) {
|
|
|
- VLog.e(TAG, "停止 tunnel 失败", e)
|
|
|
|
|
|
|
+ VLog.e(TAG, "停止 tunnel 异常", e)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
@@ -442,6 +457,18 @@ class XRayService : LifecycleVpnService() {
|
|
|
} catch (e: Exception) {
|
|
} catch (e: Exception) {
|
|
|
VLog.e(TAG, "停止 proxy connector 失败", e)
|
|
VLog.e(TAG, "停止 proxy connector 失败", e)
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // 关闭 TUN fd
|
|
|
|
|
+ tunFd?.let { tfd ->
|
|
|
|
|
+ try {
|
|
|
|
|
+ tfd.close()
|
|
|
|
|
+ VLog.i(TAG, "TUN file descriptor closed")
|
|
|
|
|
+ } catch (e: Exception) {
|
|
|
|
|
+ VLog.e(TAG, "关闭 TUN file descriptor 失败", e)
|
|
|
|
|
+ }
|
|
|
|
|
+ tunFd = null
|
|
|
|
|
+ }
|
|
|
|
|
+ VLog.i(TAG, "xray stopped")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -475,11 +502,13 @@ class XRayService : LifecycleVpnService() {
|
|
|
timerMode = mode
|
|
timerMode = mode
|
|
|
timerInitialTime = initialTime
|
|
timerInitialTime = initialTime
|
|
|
timerBaseRealtime = SystemClock.elapsedRealtime()
|
|
timerBaseRealtime = SystemClock.elapsedRealtime()
|
|
|
- timerPausedElapsed = 0L
|
|
|
|
|
- isTimerPaused = false
|
|
|
|
|
|
|
+ peekElapsedSeconds = 0L // 重置 Peek 计数器
|
|
|
|
|
+
|
|
|
|
|
+ val peekInterval = xrayConfig?.peekTimeInterval ?: 0
|
|
|
|
|
+ VLog.i(TAG, "Peek 上报间隔: ${peekInterval}秒")
|
|
|
|
|
|
|
|
timerJob = lifecycleScope.launch {
|
|
timerJob = lifecycleScope.launch {
|
|
|
- while (isActive && !isTimerPaused) {
|
|
|
|
|
|
|
+ while (isActive) {
|
|
|
// 计算从开始到现在经过的真实时间
|
|
// 计算从开始到现在经过的真实时间
|
|
|
val elapsedTime = SystemClock.elapsedRealtime() - timerBaseRealtime
|
|
val elapsedTime = SystemClock.elapsedRealtime() - timerBaseRealtime
|
|
|
|
|
|
|
@@ -513,6 +542,16 @@ class XRayService : LifecycleVpnService() {
|
|
|
// 发送计时更新广播
|
|
// 发送计时更新广播
|
|
|
sendTimerUpdate(currentTime)
|
|
sendTimerUpdate(currentTime)
|
|
|
|
|
|
|
|
|
|
+ // Peek 定时上报(屏幕关闭时不上报,节省资源)
|
|
|
|
|
+ peekElapsedSeconds++
|
|
|
|
|
+ if (peekInterval > 0 && peekElapsedSeconds >= peekInterval) {
|
|
|
|
|
+ peekElapsedSeconds = 0L
|
|
|
|
|
+ if (isScreenOn) {
|
|
|
|
|
+ AppLogger.getInstance(this@XRayService).updateAppJsonStopTime()
|
|
|
|
|
+ callPeekInterface()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// 等待1秒
|
|
// 等待1秒
|
|
|
delay(1000L)
|
|
delay(1000L)
|
|
|
}
|
|
}
|
|
@@ -528,59 +567,6 @@ class XRayService : LifecycleVpnService() {
|
|
|
}
|
|
}
|
|
|
timerJob?.cancel()
|
|
timerJob?.cancel()
|
|
|
timerJob = null
|
|
timerJob = null
|
|
|
- isTimerPaused = false
|
|
|
|
|
- timerPausedElapsed = 0L
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 暂停计时
|
|
|
|
|
- */
|
|
|
|
|
- private fun pauseTimer() {
|
|
|
|
|
- if (timerJob?.isActive == true && !isTimerPaused) {
|
|
|
|
|
- val elapsedTime = SystemClock.elapsedRealtime() - timerBaseRealtime
|
|
|
|
|
- timerPausedElapsed = elapsedTime
|
|
|
|
|
-
|
|
|
|
|
- VLog.i(TAG, "暂停计时 (已用: ${elapsedTime}ms)")
|
|
|
|
|
- isTimerPaused = true
|
|
|
|
|
- timerJob?.cancel()
|
|
|
|
|
- timerJob = null
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 恢复计时
|
|
|
|
|
- */
|
|
|
|
|
- private fun resumeTimer() {
|
|
|
|
|
- if (isTimerPaused) {
|
|
|
|
|
- VLog.i(TAG, "恢复计时 (之前已用: ${timerPausedElapsed}ms)")
|
|
|
|
|
-
|
|
|
|
|
- // 恢复时重新设置基准时间,但要减去之前已经过的时间
|
|
|
|
|
- timerBaseRealtime = SystemClock.elapsedRealtime() - timerPausedElapsed
|
|
|
|
|
- isTimerPaused = false
|
|
|
|
|
-
|
|
|
|
|
- timerJob = lifecycleScope.launch {
|
|
|
|
|
- while (isActive && !isTimerPaused) {
|
|
|
|
|
- val elapsedTime = SystemClock.elapsedRealtime() - timerBaseRealtime
|
|
|
|
|
-
|
|
|
|
|
- val currentTime = if (timerMode == TIMER_MODE_NORMAL) {
|
|
|
|
|
- timerInitialTime + elapsedTime
|
|
|
|
|
- } else {
|
|
|
|
|
- timerInitialTime - elapsedTime
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (timerMode == TIMER_MODE_COUNTDOWN && currentTime <= 0) {
|
|
|
|
|
- VLog.i(TAG, "倒计时结束 - 关闭VPN")
|
|
|
|
|
- sendTimerUpdate(0L)
|
|
|
|
|
- stop()
|
|
|
|
|
- return@launch
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- updateTimerNotification(currentTime)
|
|
|
|
|
- sendTimerUpdate(currentTime)
|
|
|
|
|
- delay(1000L)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -597,8 +583,6 @@ class XRayService : LifecycleVpnService() {
|
|
|
setPackage(packageName) // 必须设置包名,否则 RECEIVER_NOT_EXPORTED 接收不到
|
|
setPackage(packageName) // 必须设置包名,否则 RECEIVER_NOT_EXPORTED 接收不到
|
|
|
putExtra("currentTime", currentTime)
|
|
putExtra("currentTime", currentTime)
|
|
|
putExtra("mode", timerMode)
|
|
putExtra("mode", timerMode)
|
|
|
- putExtra("isRunning", timerJob?.isActive == true)
|
|
|
|
|
- putExtra("isPaused", isTimerPaused)
|
|
|
|
|
}
|
|
}
|
|
|
sendBroadcast(intent)
|
|
sendBroadcast(intent)
|
|
|
}
|
|
}
|
|
@@ -608,14 +592,11 @@ class XRayService : LifecycleVpnService() {
|
|
|
*/
|
|
*/
|
|
|
private fun updateTimerNotification(currentTime: Long) {
|
|
private fun updateTimerNotification(currentTime: Long) {
|
|
|
val timeText = formatTime(currentTime)
|
|
val timeText = formatTime(currentTime)
|
|
|
- val modeText = if (timerMode == TIMER_MODE_NORMAL) "计时中" else "倒计时"
|
|
|
|
|
- val statusText = if (isTimerPaused) "已暂停" else "运行中"
|
|
|
|
|
-
|
|
|
|
|
val notification = createConnectionNotification(
|
|
val notification = createConnectionNotification(
|
|
|
this,
|
|
this,
|
|
|
NOTIFICATION_CHANNEL_ID,
|
|
NOTIFICATION_CHANNEL_ID,
|
|
|
- "NOMO VPN $modeText",
|
|
|
|
|
- "$timeText - $statusText",
|
|
|
|
|
|
|
+ "NOMO VPN",
|
|
|
|
|
+ "Running - $timeText",
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
val notificationManager = getSystemService(NotificationManager::class.java)
|
|
val notificationManager = getSystemService(NotificationManager::class.java)
|
|
@@ -631,7 +612,45 @@ class XRayService : LifecycleVpnService() {
|
|
|
private fun dealStat() {
|
|
private fun dealStat() {
|
|
|
val queryTypes = byteArrayOf(1, 2, 3)
|
|
val queryTypes = byteArrayOf(1, 2, 3)
|
|
|
val stats: String? = Ixvpn_mobile.proxyConnectorQueryStats(queryTypes)
|
|
val stats: String? = Ixvpn_mobile.proxyConnectorQueryStats(queryTypes)
|
|
|
|
|
+ val version = Ixvpn_mobile.proxyConnectorVersion()
|
|
|
VLog.i(Constant.TAG, "stats = $stats")
|
|
VLog.i(Constant.TAG, "stats = $stats")
|
|
|
|
|
+
|
|
|
|
|
+ // 将stats和version写入core.json日志文件
|
|
|
|
|
+ CoreLogger.getInstance(this).updateStatsAndVersion(stats, version)
|
|
|
|
|
+ // 更新停止时间
|
|
|
|
|
+ AppLogger.getInstance(this).updateAppJsonStopTime()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 调用 Peek 接口上报数据
|
|
|
|
|
+ */
|
|
|
|
|
+ private fun callPeekInterface() {
|
|
|
|
|
+ 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
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ // 上报数据
|
|
|
|
|
+ NetworkReporter.getInstance().report(
|
|
|
|
|
+ "/api/v1/event",
|
|
|
|
|
+ "NM_PeekLog",
|
|
|
|
|
+ null,
|
|
|
|
|
+ null
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ VLog.i(TAG, "Peek log reported successfully")
|
|
|
|
|
+ } catch (e: Exception) {
|
|
|
|
|
+ VLog.e(TAG, "callPeekInterface error: ${e.message}")
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|