V2RayServiceManager.kt 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. package com.v2ray.ang.handler
  2. import android.app.Service
  3. import android.content.BroadcastReceiver
  4. import android.content.Context
  5. import android.content.Intent
  6. import android.content.IntentFilter
  7. import android.os.Build
  8. import android.util.Log
  9. import androidx.core.content.ContextCompat
  10. import com.v2ray.ang.AppConfig
  11. import com.v2ray.ang.R
  12. import com.v2ray.ang.dto.EConfigType
  13. import com.v2ray.ang.dto.ProfileItem
  14. import com.v2ray.ang.extension.toast
  15. import com.v2ray.ang.service.ServiceControl
  16. import com.v2ray.ang.service.V2RayProxyOnlyService
  17. import com.v2ray.ang.service.V2RayVpnService
  18. import com.v2ray.ang.util.MessageUtil
  19. import com.v2ray.ang.handler.PluginServiceManager
  20. import com.v2ray.ang.util.Utils
  21. import go.Seq
  22. import kotlinx.coroutines.CoroutineScope
  23. import kotlinx.coroutines.Dispatchers
  24. import kotlinx.coroutines.launch
  25. import libv2ray.CoreCallbackHandler
  26. import libv2ray.CoreController
  27. import libv2ray.Libv2ray
  28. import java.lang.ref.SoftReference
  29. object V2RayServiceManager {
  30. private val coreController: CoreController = Libv2ray.newCoreController(CoreCallback())
  31. private val mMsgReceive = ReceiveMessageHandler()
  32. private var currentConfig: ProfileItem? = null
  33. var serviceControl: SoftReference<ServiceControl>? = null
  34. set(value) {
  35. field = value
  36. Seq.setContext(value?.get()?.getService()?.applicationContext)
  37. Libv2ray.initCoreEnv(Utils.userAssetPath(value?.get()?.getService()), Utils.getDeviceIdForXUDPBaseKey())
  38. }
  39. /**
  40. * Starts the V2Ray service from a toggle action.
  41. * @param context The context from which the service is started.
  42. * @return True if the service was started successfully, false otherwise.
  43. */
  44. fun startVServiceFromToggle(context: Context): Boolean {
  45. if (MmkvManager.getSelectServer().isNullOrEmpty()) {
  46. context.toast(R.string.app_tile_first_use)
  47. return false
  48. }
  49. startContextService(context)
  50. return true
  51. }
  52. /**
  53. * Starts the V2Ray service.
  54. * @param context The context from which the service is started.
  55. * @param guid The GUID of the server configuration to use (optional).
  56. */
  57. fun startVService(context: Context, guid: String? = null) {
  58. if (guid != null) {
  59. MmkvManager.setSelectServer(guid)
  60. }
  61. startContextService(context)
  62. }
  63. /**
  64. * Stops the V2Ray service.
  65. * @param context The context from which the service is stopped.
  66. */
  67. fun stopVService(context: Context) {
  68. context.toast(R.string.toast_services_stop)
  69. MessageUtil.sendMsg2Service(context, AppConfig.MSG_STATE_STOP, "")
  70. }
  71. /**
  72. * Checks if the V2Ray service is running.
  73. * @return True if the service is running, false otherwise.
  74. */
  75. fun isRunning() = coreController.isRunning
  76. /**
  77. * Gets the name of the currently running server.
  78. * @return The name of the running server.
  79. */
  80. fun getRunningServerName() = currentConfig?.remarks.orEmpty()
  81. /**
  82. * Starts the context service for V2Ray.
  83. * Chooses between VPN service or Proxy-only service based on user settings.
  84. * @param context The context from which the service is started.
  85. */
  86. private fun startContextService(context: Context) {
  87. if (coreController.isRunning) {
  88. return
  89. }
  90. val guid = MmkvManager.getSelectServer() ?: return
  91. val config = MmkvManager.decodeServerConfig(guid) ?: return
  92. if (config.configType != EConfigType.CUSTOM
  93. && !Utils.isValidUrl(config.server)
  94. && !Utils.isPureIpAddress(config.server.orEmpty())
  95. ) return
  96. // val result = V2rayConfigUtil.getV2rayConfig(context, guid)
  97. // if (!result.status) return
  98. if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PROXY_SHARING) == true) {
  99. context.toast(R.string.toast_warning_pref_proxysharing_short)
  100. } else {
  101. context.toast(R.string.toast_services_start)
  102. }
  103. val intent = if ((MmkvManager.decodeSettingsString(AppConfig.PREF_MODE) ?: AppConfig.VPN) == AppConfig.VPN) {
  104. Intent(context.applicationContext, V2RayVpnService::class.java)
  105. } else {
  106. Intent(context.applicationContext, V2RayProxyOnlyService::class.java)
  107. }
  108. if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) {
  109. context.startForegroundService(intent)
  110. } else {
  111. context.startService(intent)
  112. }
  113. }
  114. /**
  115. * Refer to the official documentation for [registerReceiver](https://developer.android.com/reference/androidx/core/content/ContextCompat#registerReceiver(android.content.Context,android.content.BroadcastReceiver,android.content.IntentFilter,int):
  116. * `registerReceiver(Context, BroadcastReceiver, IntentFilter, int)`.
  117. * Starts the V2Ray core service.
  118. */
  119. fun startCoreLoop(): Boolean {
  120. if (coreController.isRunning) {
  121. return false
  122. }
  123. val service = getService() ?: return false
  124. val guid = MmkvManager.getSelectServer() ?: return false
  125. val config = MmkvManager.decodeServerConfig(guid) ?: return false
  126. val result = V2rayConfigManager.getV2rayConfig(service, guid)
  127. if (!result.status)
  128. return false
  129. try {
  130. val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)
  131. mFilter.addAction(Intent.ACTION_SCREEN_ON)
  132. mFilter.addAction(Intent.ACTION_SCREEN_OFF)
  133. mFilter.addAction(Intent.ACTION_USER_PRESENT)
  134. ContextCompat.registerReceiver(service, mMsgReceive, mFilter, Utils.receiverFlags())
  135. } catch (e: Exception) {
  136. Log.e(AppConfig.TAG, "Failed to register broadcast receiver", e)
  137. return false
  138. }
  139. currentConfig = config
  140. try {
  141. coreController.startLoop(result.content)
  142. } catch (e: Exception) {
  143. Log.e(AppConfig.TAG, "Failed to start Core loop", e)
  144. return false
  145. }
  146. if (coreController.isRunning == false) {
  147. MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_FAILURE, "")
  148. NotificationManager.cancelNotification()
  149. return false
  150. }
  151. try {
  152. MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_SUCCESS, "")
  153. NotificationManager.showNotification(currentConfig)
  154. NotificationManager.startSpeedNotification(currentConfig)
  155. PluginServiceManager.runPlugin(service, config, result.socksPort)
  156. } catch (e: Exception) {
  157. Log.e(AppConfig.TAG, "Failed to startup service", e)
  158. return false
  159. }
  160. return true
  161. }
  162. /**
  163. * Stops the V2Ray core service.
  164. * Unregisters broadcast receivers, stops notifications, and shuts down plugins.
  165. * @return True if the core was stopped successfully, false otherwise.
  166. */
  167. fun stopCoreLoop(): Boolean {
  168. val service = getService() ?: return false
  169. if (coreController.isRunning) {
  170. CoroutineScope(Dispatchers.IO).launch {
  171. try {
  172. coreController.stopLoop()
  173. } catch (e: Exception) {
  174. Log.e(AppConfig.TAG, "Failed to stop V2Ray loop", e)
  175. }
  176. }
  177. }
  178. MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_STOP_SUCCESS, "")
  179. NotificationManager.cancelNotification()
  180. try {
  181. service.unregisterReceiver(mMsgReceive)
  182. } catch (e: Exception) {
  183. Log.e(AppConfig.TAG, "Failed to unregister broadcast receiver", e)
  184. }
  185. PluginServiceManager.stopPlugin()
  186. return true
  187. }
  188. /**
  189. * Queries the statistics for a given tag and link.
  190. * @param tag The tag to query.
  191. * @param link The link to query.
  192. * @return The statistics value.
  193. */
  194. fun queryStats(tag: String, link: String): Long {
  195. return coreController.queryStats(tag, link)
  196. }
  197. /**
  198. * Measures the connection delay for the current V2Ray configuration.
  199. * Tests with primary URL first, then falls back to alternative URL if needed.
  200. * Also fetches remote IP information if the delay test was successful.
  201. */
  202. private fun measureV2rayDelay() {
  203. if (coreController.isRunning == false) {
  204. return
  205. }
  206. CoroutineScope(Dispatchers.IO).launch {
  207. val service = getService() ?: return@launch
  208. var time = -1L
  209. var errorStr = ""
  210. try {
  211. time = coreController.measureDelay(SettingsManager.getDelayTestUrl())
  212. } catch (e: Exception) {
  213. Log.e(AppConfig.TAG, "Failed to measure delay with primary URL", e)
  214. errorStr = e.message?.substringAfter("\":") ?: "empty message"
  215. }
  216. if (time == -1L) {
  217. try {
  218. time = coreController.measureDelay(SettingsManager.getDelayTestUrl(true))
  219. } catch (e: Exception) {
  220. Log.e(AppConfig.TAG, "Failed to measure delay with alternative URL", e)
  221. errorStr = e.message?.substringAfter("\":") ?: "empty message"
  222. }
  223. }
  224. val result = if (time >= 0) {
  225. service.getString(R.string.connection_test_available, time)
  226. } else {
  227. service.getString(R.string.connection_test_error, errorStr)
  228. }
  229. MessageUtil.sendMsg2UI(service, AppConfig.MSG_MEASURE_DELAY_SUCCESS, result)
  230. // Only fetch IP info if the delay test was successful
  231. if (time >= 0) {
  232. SpeedtestManager.getRemoteIPInfo()?.let { ip ->
  233. MessageUtil.sendMsg2UI(service, AppConfig.MSG_MEASURE_DELAY_SUCCESS, "$result\n$ip")
  234. }
  235. }
  236. }
  237. }
  238. /**
  239. * Gets the current service instance.
  240. * @return The current service instance, or null if not available.
  241. */
  242. private fun getService(): Service? {
  243. return serviceControl?.get()?.getService()
  244. }
  245. /**
  246. * Core callback handler implementation for handling V2Ray core events.
  247. * Handles startup, shutdown, socket protection, and status emission.
  248. */
  249. private class CoreCallback : CoreCallbackHandler {
  250. /**
  251. * Called when V2Ray core starts up.
  252. * @return 0 for success, any other value for failure.
  253. */
  254. override fun startup(): Long {
  255. return 0
  256. }
  257. /**
  258. * Called when V2Ray core shuts down.
  259. * @return 0 for success, any other value for failure.
  260. */
  261. override fun shutdown(): Long {
  262. val serviceControl = serviceControl?.get() ?: return -1
  263. return try {
  264. serviceControl.stopService()
  265. 0
  266. } catch (e: Exception) {
  267. Log.e(AppConfig.TAG, "Failed to stop service in callback", e)
  268. -1
  269. }
  270. }
  271. /**
  272. * Called when V2Ray core emits status information.
  273. * @param l Status code.
  274. * @param s Status message.
  275. * @return Always returns 0.
  276. */
  277. override fun onEmitStatus(l: Long, s: String?): Long {
  278. return 0
  279. }
  280. }
  281. /**
  282. * Broadcast receiver for handling messages sent to the service.
  283. * Handles registration, service control, and screen events.
  284. */
  285. private class ReceiveMessageHandler : BroadcastReceiver() {
  286. /**
  287. * Handles received broadcast messages.
  288. * Processes service control messages and screen state changes.
  289. * @param ctx The context in which the receiver is running.
  290. * @param intent The intent being received.
  291. */
  292. override fun onReceive(ctx: Context?, intent: Intent?) {
  293. val serviceControl = serviceControl?.get() ?: return
  294. when (intent?.getIntExtra("key", 0)) {
  295. AppConfig.MSG_REGISTER_CLIENT -> {
  296. if (coreController.isRunning) {
  297. MessageUtil.sendMsg2UI(serviceControl.getService(), AppConfig.MSG_STATE_RUNNING, "")
  298. } else {
  299. MessageUtil.sendMsg2UI(serviceControl.getService(), AppConfig.MSG_STATE_NOT_RUNNING, "")
  300. }
  301. }
  302. AppConfig.MSG_UNREGISTER_CLIENT -> {
  303. // nothing to do
  304. }
  305. AppConfig.MSG_STATE_START -> {
  306. // nothing to do
  307. }
  308. AppConfig.MSG_STATE_STOP -> {
  309. Log.i(AppConfig.TAG, "Stop Service")
  310. serviceControl.stopService()
  311. }
  312. AppConfig.MSG_STATE_RESTART -> {
  313. Log.i(AppConfig.TAG, "Restart Service")
  314. serviceControl.stopService()
  315. Thread.sleep(500L)
  316. startVService(serviceControl.getService())
  317. }
  318. AppConfig.MSG_MEASURE_DELAY -> {
  319. measureV2rayDelay()
  320. }
  321. }
  322. when (intent?.action) {
  323. Intent.ACTION_SCREEN_OFF -> {
  324. Log.i(AppConfig.TAG, "SCREEN_OFF, stop querying stats")
  325. NotificationManager.stopSpeedNotification(currentConfig)
  326. }
  327. Intent.ACTION_SCREEN_ON -> {
  328. Log.i(AppConfig.TAG, "SCREEN_ON, start querying stats")
  329. NotificationManager.startSpeedNotification(currentConfig)
  330. }
  331. }
  332. }
  333. }
  334. }