Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions V2rayNG/app/src/main/java/com/v2ray/ang/core/CoreConfigManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.text.TextUtils
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.dto.ConfigResult
import com.v2ray.ang.dto.CoreConfigContext
import com.v2ray.ang.dto.V2rayConfig
Expand Down Expand Up @@ -34,7 +35,11 @@ object CoreConfigManager {
fun getV2rayConfig(context: Context, guid: String): ConfigResult {
try {
val configContext = CoreConfigContextBuilder.build(context, guid)
?: return ConfigResult(status = false, guid = guid, errorMessage = "Failed to build config context")
?: return ConfigResult(
status = false,
guid = guid,
errorMessage = context.getString(R.string.core_error_build_config_context)
)
if (configContext.isCustom) {
return buildV2rayCustomConfig(configContext)
}
Expand All @@ -44,7 +49,10 @@ object CoreConfigManager {
return ConfigResult(
status = false,
guid = guid,
errorMessage = "Failed to get V2ray config: ${e.message ?: e.javaClass.simpleName}"
errorMessage = context.getString(
R.string.core_error_get_config_detail,
e.message ?: e.javaClass.simpleName
)
)
}
}
Expand All @@ -57,7 +65,11 @@ object CoreConfigManager {
fun getV2rayConfig4Speedtest(context: Context, guid: String): ConfigResult {
try {
val configContext = CoreConfigContextBuilder.build(context, guid)
?: return ConfigResult(status = false, guid = guid, errorMessage = "Failed to build config context")
?: return ConfigResult(
status = false,
guid = guid,
errorMessage = context.getString(R.string.core_error_build_config_context)
)
if (configContext.isCustom) {
return buildV2rayCustomConfig(configContext)
}
Expand All @@ -70,7 +82,10 @@ object CoreConfigManager {
return ConfigResult(
status = false,
guid = guid,
errorMessage = "Failed to get V2ray config for speedtest: ${e.message ?: e.javaClass.simpleName}"
errorMessage = context.getString(
R.string.core_error_get_speedtest_config_detail,
e.message ?: e.javaClass.simpleName
)
)
}
}
Expand All @@ -81,7 +96,11 @@ object CoreConfigManager {
private fun buildV2rayCustomConfig(configContext: CoreConfigContext): ConfigResult {
val context = configContext.context
val raw = MmkvManager.decodeServerRaw(configContext.guid)
?: return ConfigResult(status = false, guid = configContext.guid, errorMessage = "Custom config is empty")
?: return ConfigResult(
status = false,
guid = configContext.guid,
errorMessage = context.getString(R.string.core_error_custom_config_empty)
)
val result = ConfigResult(true, configContext.guid, raw)

val json = JsonUtil.parseString(raw)?.takeIf { it.isJsonObject }?.asJsonObject ?: return result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,12 @@ object CoreNativeManager {
*
* @return Version string of the V2Ray core
*/
fun getLibVersion(): String {
fun getLibVersion(): String? {
return try {
Libv2ray.checkVersionX()
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "Failed to check V2Ray version", e)
"Unknown"
null
}
}

Expand Down
18 changes: 11 additions & 7 deletions V2rayNG/app/src/main/java/com/v2ray/ang/core/CoreServiceManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,16 @@ object CoreServiceManager {

@Throws(Exception::class)
private fun launchCore(service: Service, vpnInterface: ParcelFileDescriptor?, isReload: Boolean = false) {
val guid = MmkvManager.getSelectServer() ?: error("No server selected")
val config = MmkvManager.decodeServerConfig(guid) ?: error("Failed to decode server config")
val guid = MmkvManager.getSelectServer()
?: error(service.getString(R.string.core_error_no_server_selected))
val config = MmkvManager.decodeServerConfig(guid)
?: error(service.getString(R.string.core_error_decode_server_config))

LogUtil.i(AppConfig.TAG, "StartCore-Manager: Starting core loop for ${config.remarks}")
val result = CoreConfigManager.getV2rayConfig(service, guid)
LogUtil.d(AppConfig.TAG, result.content)
if (!result.status) {
error(result.errorMessage.ifBlank { "Failed to get V2Ray config" })
error(result.errorMessage.ifBlank { service.getString(R.string.core_error_get_config) })
}

currentConfig = config
Expand All @@ -149,7 +151,7 @@ object CoreServiceManager {
coreController.startLoop(result.content, tunFd)

if (!isRunning()) {
error("Core failed to start")
error(service.getString(R.string.core_error_start))
}

if (browserDialer != null) {
Expand Down Expand Up @@ -321,14 +323,16 @@ object CoreServiceManager {
time = coreController.measureDelay(SettingsManager.getDelayTestUrl())
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "StartCore-Manager: Failed to measure delay", e)
errorStr = e.message?.substringAfter("\":") ?: "empty message"
errorStr = e.message?.substringAfter("\":")
?: service.getString(R.string.connection_test_empty_message)
}
if (time == -1L) {
try {
time = coreController.measureDelay(SettingsManager.getDelayTestUrl(true))
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "StartCore-Manager: Failed to measure delay", e)
errorStr = e.message?.substringAfter("\":") ?: "empty message"
errorStr = e.message?.substringAfter("\":")
?: service.getString(R.string.connection_test_empty_message)
}
}

Expand All @@ -341,7 +345,7 @@ object CoreServiceManager {

// Only fetch IP info if the delay test was successful
if (time >= 0) {
SpeedtestManager.getRemoteIPInfo()?.let { ip ->
SpeedtestManager.getRemoteIPInfo(service.getString(R.string.value_unknown))?.let { ip ->
MessageHelper.sendMsg2UI(service, AppConfig.MSG_MEASURE_DELAY_SUCCESS, "$result\n$ip")
}
}
Expand Down
13 changes: 8 additions & 5 deletions V2rayNG/app/src/main/java/com/v2ray/ang/core/LauncherManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,40 @@ import com.v2ray.ang.service.CoreProxyOnlyService
import com.v2ray.ang.service.CoreRootService
import com.v2ray.ang.service.CoreVpnService
import com.v2ray.ang.util.LogUtil
import com.v2ray.ang.util.MyContextWrapper
import com.v2ray.ang.util.Utils

object LauncherManager {

fun startServiceFromToggle(context: Context): Boolean {
val localizedContext = MyContextWrapper.wrap(context, SettingsManager.getLocale())
if (MmkvManager.getSelectServer().isNullOrEmpty()) {
context.toast(R.string.app_tile_first_use)
localizedContext.toast(R.string.app_tile_first_use)
return false
}
try {
startContextService(context)
startContextService(localizedContext)
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "LauncherManager: ${e.message}", e)
context.toast(e.message ?: e.javaClass.simpleName)
localizedContext.toast(e.message ?: e.javaClass.simpleName)
return false
}
return true
}

fun startService(context: Context, guid: String? = null) {
LogUtil.i(AppConfig.TAG, "LauncherManager: startService from ${context::class.java.simpleName}")
val localizedContext = MyContextWrapper.wrap(context, SettingsManager.getLocale())

if (guid != null) {
MmkvManager.setSelectServer(guid)
}

try {
startContextService(context)
startContextService(localizedContext)
} catch (e: Exception) {
LogUtil.e(AppConfig.TAG, "LauncherManager: ${e.message}", e)
context.toast(e.message ?: e.javaClass.simpleName)
localizedContext.toast(e.message ?: e.javaClass.simpleName)
}
}

Expand Down
13 changes: 8 additions & 5 deletions V2rayNG/app/src/main/java/com/v2ray/ang/enums/PermissionType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.v2ray.ang.enums
import android.Manifest
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import com.v2ray.ang.R

/**
* Permission types used in the app, handling API level differences.
Expand All @@ -28,12 +30,13 @@ enum class PermissionType {
/** Return the actual Android permission string */
abstract fun getPermission(): String

/** Return a human-readable label for the permission */
fun getLabel(): String {
/** Return the string resource for the human-readable permission label. */
@StringRes
fun getLabelRes(): Int {
return when (this) {
CAMERA -> "Camera"
POST_NOTIFICATIONS -> "Notification"
ACCESS_LOCAL_NETWORK -> "Local Network"
CAMERA -> R.string.permission_camera
POST_NOTIFICATIONS -> R.string.permission_notification
ACCESS_LOCAL_NETWORK -> R.string.permission_local_network
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ object SpeedtestManager {
return -1
}

fun getRemoteIPInfo(): String? {
fun getRemoteIPInfo(unknownLabel: String): String? {
val url = MmkvManager.decodeSettingsString(AppConfig.PREF_IP_API_URL)
.takeIf { !it.isNullOrBlank() } ?: AppConfig.IP_API_URL

Expand Down Expand Up @@ -81,6 +81,6 @@ object SpeedtestManager {
ipInfo.location?.country_code
).firstOrNull { !it.isNullOrBlank() }

return "(${country ?: "unknown"}) ${ip ?: "unknown"}"
return "(${country ?: unknownLabel}) ${ip ?: unknownLabel}"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ class PermissionHelper(private val activity: ComponentActivity) {
if (isGranted) {
onGranted()
} else {
val message = "${activity.getString(R.string.toast_permission_denied)} ${permissionType.getLabel()}"
val message = activity.getString(
R.string.toast_permission_denied_for,
activity.getString(permissionType.getLabelRes())
)
activity.toast(message)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.v2ray.ang.service

import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.IBinder
import androidx.core.app.NotificationCompat
Expand All @@ -14,13 +15,22 @@ import com.v2ray.ang.enums.NotificationChannelType
import com.v2ray.ang.extension.serializable
import com.v2ray.ang.handler.AngConfigManager
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.helper.MessageHelper
import com.v2ray.ang.helper.NotificationHelper
import com.v2ray.ang.util.LogUtil
import com.v2ray.ang.util.MyContextWrapper
import java.util.Collections

class CoreTestService : Service() {

override fun attachBaseContext(newBase: Context?) {
val context = newBase?.let {
MyContextWrapper.wrap(it, SettingsManager.getLocale())
}
super.attachBaseContext(context)
}

// manage active batch workers so each batch is independent and cancellable
private val activeWorkers = Collections.synchronizedList(mutableListOf<RealPingWorkerService>())
private val cancelAction by lazy {
Expand Down Expand Up @@ -134,7 +144,7 @@ class CoreTestService : Service() {
channelType = NotificationChannelType.CORE_TEST,
context = this,
title = getString(R.string.app_name),
content = getString(R.string.connection_runing_task_left, event.text)
content = getString(R.string.connection_running_task_left, event.text)
)
MessageHelper.sendMsg2UI(this, AppConfig.MSG_MEASURE_CONFIG_NOTIFY, event.text)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import com.v2ray.ang.enums.NotificationChannelType
import com.v2ray.ang.extension.serializable
import com.v2ray.ang.handler.AngConfigManager
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.helper.NotificationHelper
import com.v2ray.ang.util.LogUtil
import com.v2ray.ang.util.MyContextWrapper
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand All @@ -38,6 +40,13 @@ class SubscriptionUpdateService : Service() {

private val updateSemaphore = Semaphore(2)

override fun attachBaseContext(newBase: Context?) {
val context = newBase?.let {
MyContextWrapper.wrap(it, SettingsManager.getLocale())
}
super.attachBaseContext(context)
}

override fun onCreate() {
super.onCreate()
CoreNativeManager.initCoreEnv(this)
Expand Down Expand Up @@ -118,7 +127,7 @@ class SubscriptionUpdateService : Service() {
showNotification(
context = this,
titleResId = R.string.title_pref_auto_update_subscription,
content = "Updating ${subItem.remarks}"
content = getString(R.string.subscription_update_updating, subItem.remarks)
)

if (forcedUpdate || MmkvManager.decodeSettingsBool(AppConfig.PREF_UPDATE_SUBSCRIPTION, false)) {
Expand Down Expand Up @@ -184,7 +193,7 @@ class SubscriptionUpdateService : Service() {
private fun handleWorkerEvent(event: RealPingEvent, remarks: String, onWorkerDone: () -> Unit) {
when (event) {
is RealPingEvent.Progress -> {
val text = "${event.text} in $remarks"
val text = getString(R.string.subscription_update_progress, event.text, remarks)
showNotification(
context = this,
titleResId = R.string.title_real_ping_all_server,
Expand Down
3 changes: 2 additions & 1 deletion V2rayNG/app/src/main/java/com/v2ray/ang/ui/AboutActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ fun AboutScreen(onBackClick: () -> Unit) {
val context = LocalContext.current
var showOssDialog by remember { mutableStateOf(false) }

val versionText = "v${BuildConfig.VERSION_NAME} (${CoreNativeManager.getLibVersion()})"
val libVersion = CoreNativeManager.getLibVersion() ?: stringResource(R.string.value_unknown)
val versionText = "v${BuildConfig.VERSION_NAME} ($libVersion)"
val appIdText = BuildConfig.APPLICATION_ID

Scaffold(
Expand Down
13 changes: 9 additions & 4 deletions V2rayNG/app/src/main/java/com/v2ray/ang/ui/ScannerActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,9 @@ fun ScannerScreen(
if (isScanning) R.drawable.ic_stop_24dp
else R.drawable.ic_scan_24dp
),
contentDescription = if (isScanning) "stop scan" else "start scan"
contentDescription = stringResource(
if (isScanning) R.string.acc_stop_scanner else R.string.acc_start_scanner
)
)
}
if (isScanning && hasTorch) {
Expand All @@ -194,14 +196,17 @@ fun ScannerScreen(
if (torchEnabled) R.drawable.ic_flash_on_24dp
else R.drawable.ic_flash_off_24dp
),
contentDescription = "Torch"
contentDescription = stringResource(
if (torchEnabled) R.string.acc_turn_torch_off
else R.string.acc_turn_torch_on
)
)
}
}
IconButton(onClick = onSelectPhoto) {
Icon(
painterResource(R.drawable.ic_image_24dp),
contentDescription = "select image"
contentDescription = stringResource(R.string.acc_select_image)
)
}
}
Expand Down Expand Up @@ -245,7 +250,7 @@ private fun ScannerIdlePlaceholder(onStartClick: () -> Unit) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Icon(
painter = painterResource(R.drawable.ic_scan_24dp),
contentDescription = "Start Scanner",
contentDescription = stringResource(R.string.acc_start_scanner),
modifier = Modifier.size(80.dp),
tint = MaterialTheme.colorScheme.primary
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ fun AppPickerScreen(
IconButton(onClick = { showSearch = true }) {
Icon(
painterResource(R.drawable.ic_search_24dp),
contentDescription = stringResource(R.string.menu_item_search)
contentDescription = stringResource(R.string.acc_search)
)
}
}
Expand Down
Loading