diff --git a/V2rayNG/app/src/main/AndroidManifest.xml b/V2rayNG/app/src/main/AndroidManifest.xml index 999a105b..7fbb2513 100644 --- a/V2rayNG/app/src/main/AndroidManifest.xml +++ b/V2rayNG/app/src/main/AndroidManifest.xml @@ -282,6 +282,8 @@ android:label="BootReceiver"> + + diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/receiver/BootReceiver.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/receiver/BootReceiver.kt index ac755db3..fb3b9e7d 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/receiver/BootReceiver.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/receiver/BootReceiver.kt @@ -3,6 +3,7 @@ package com.v2ray.ang.receiver import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.os.UserManager import com.v2ray.ang.AppConfig import com.v2ray.ang.core.CoreServiceManager import com.v2ray.ang.handler.MmkvManager @@ -12,18 +13,34 @@ import com.v2ray.ang.util.LogUtil class BootReceiver : BroadcastReceiver() { /** * This method is called when the BroadcastReceiver is receiving an Intent broadcast. - * It checks if the context is not null and the action is ACTION_BOOT_COMPLETED. + * It handles BOOT_COMPLETED, LOCKED_BOOT_COMPLETED, and MY_PACKAGE_REPLACED. * If the conditions are met, it starts the V2Ray service. * * @param context The Context in which the receiver is running. * @param intent The Intent being received. */ override fun onReceive(context: Context?, intent: Intent?) { - LogUtil.i(AppConfig.TAG, "BootReceiver received: ${intent?.action}") + val action = intent?.action ?: return + if (context == null) return - if (context == null || intent?.action != Intent.ACTION_BOOT_COMPLETED) { - LogUtil.w(AppConfig.TAG, "BootReceiver: Invalid context or action") - return + LogUtil.i(AppConfig.TAG, "BootReceiver received: $action") + + when (action) { + Intent.ACTION_BOOT_COMPLETED, + Intent.ACTION_MY_PACKAGE_REPLACED -> { + // Continue + } + Intent.ACTION_LOCKED_BOOT_COMPLETED -> { + val userManager = context.getSystemService(Context.USER_SERVICE) as? UserManager + if (userManager != null && !userManager.isUserUnlocked) { + LogUtil.w(AppConfig.TAG, "BootReceiver: User is locked, skipping auto start") + return + } + } + else -> { + LogUtil.w(AppConfig.TAG, "BootReceiver: Unhandled action: $action") + return + } } if (!MmkvManager.decodeStartOnBoot()) { diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/service/CoreVpnService.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/service/CoreVpnService.kt index abb39ae6..6b868cc8 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/service/CoreVpnService.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/service/CoreVpnService.kt @@ -116,20 +116,26 @@ class CoreVpnService : VpnService(), ServiceControl { } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + // Always-on VPN restarts from OS deliver intent.action == SERVICE_INTERFACE or null intent. + // Reset any stuck start lock left by a killed process to allow setupVpnService() to run. + val isSystemVpnStart = intent == null || intent.action == SERVICE_INTERFACE + if (isSystemVpnStart) { + unlockStart() + } if (!tryLockStart()) { LogUtil.w(AppConfig.TAG, "StartCore-VPN: Start already in progress") return START_NOT_STICKY } - LogUtil.i(AppConfig.TAG, "StartCore-VPN: Service command received") + LogUtil.i(AppConfig.TAG, "StartCore-VPN: Service command received, systemVpnStart=$isSystemVpnStart") NotificationManager.showNotification(null) if (!setupVpnService()) { unlockStart() + // Stop service if setup fails to avoid infinite restart loops (START_STICKY) stopSelf() return START_NOT_STICKY } startService() return START_STICKY - //return super.onStartCommand(intent, flags, startId) } override fun getService(): Service {