release
This commit is contained in:
parent
91042c083f
commit
f24dd7cfe9
1
.idea/.name
generated
Normal file
1
.idea/.name
generated
Normal file
@ -0,0 +1 @@
|
|||||||
|
punchnet
|
||||||
6
.idea/AndroidProjectSystem.xml
generated
Normal file
6
.idea/AndroidProjectSystem.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AndroidProjectSystem">
|
||||||
|
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
4
.idea/inspectionProfiles/Project_Default.xml
generated
4
.idea/inspectionProfiles/Project_Default.xml
generated
@ -49,6 +49,10 @@
|
|||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
<option name="previewFile" value="true" />
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
<option name="previewFile" value="true" />
|
<option name="previewFile" value="true" />
|
||||||
|
|||||||
1
.idea/misc.xml
generated
1
.idea/misc.xml
generated
@ -1,4 +1,3 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||||
|
|||||||
BIN
app/release/app-release.apk
Normal file
BIN
app/release/app-release.apk
Normal file
Binary file not shown.
BIN
app/release/baselineProfiles/0/app-release.dm
Normal file
BIN
app/release/baselineProfiles/0/app-release.dm
Normal file
Binary file not shown.
BIN
app/release/baselineProfiles/1/app-release.dm
Normal file
BIN
app/release/baselineProfiles/1/app-release.dm
Normal file
Binary file not shown.
37
app/release/output-metadata.json
Normal file
37
app/release/output-metadata.json
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"artifactType": {
|
||||||
|
"type": "APK",
|
||||||
|
"kind": "Directory"
|
||||||
|
},
|
||||||
|
"applicationId": "com.jihe.punchnet",
|
||||||
|
"variantName": "release",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "SINGLE",
|
||||||
|
"filters": [],
|
||||||
|
"attributes": [],
|
||||||
|
"versionCode": 1,
|
||||||
|
"versionName": "1.0",
|
||||||
|
"outputFile": "app-release.apk"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"elementType": "File",
|
||||||
|
"baselineProfiles": [
|
||||||
|
{
|
||||||
|
"minApi": 28,
|
||||||
|
"maxApi": 30,
|
||||||
|
"baselineProfiles": [
|
||||||
|
"baselineProfiles/1/app-release.dm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minApi": 31,
|
||||||
|
"maxApi": 2147483647,
|
||||||
|
"baselineProfiles": [
|
||||||
|
"baselineProfiles/0/app-release.dm"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"minSdkVersionForDexing": 26
|
||||||
|
}
|
||||||
@ -11,7 +11,7 @@
|
|||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@drawable/punchnet_log"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
|
|||||||
@ -12,6 +12,9 @@ import android.os.ParcelFileDescriptor
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import com.jihe.punchnet.data.RouteItem
|
||||||
|
import com.jihe.punchnet.data.RouteViewModel
|
||||||
import com.jihe.punchnet.sdlan.config.Arguments
|
import com.jihe.punchnet.sdlan.config.Arguments
|
||||||
import com.jihe.punchnet.sdlan.config.toIPV4String
|
import com.jihe.punchnet.sdlan.config.toIPV4String
|
||||||
import com.jihe.punchnet.sdlan.logs.TerminalLogger
|
import com.jihe.punchnet.sdlan.logs.TerminalLogger
|
||||||
@ -23,7 +26,9 @@ import com.jihe.punchnet.sdlan.network.RouteDetail
|
|||||||
import com.jihe.punchnet.sdlan.network.RouteTable
|
import com.jihe.punchnet.sdlan.network.RouteTable
|
||||||
import com.jihe.punchnet.sdlan.network.cidrToRouteDetail
|
import com.jihe.punchnet.sdlan.network.cidrToRouteDetail
|
||||||
import com.jihe.punchnet.sdlan.network.ipInt2ByteArray
|
import com.jihe.punchnet.sdlan.network.ipInt2ByteArray
|
||||||
|
import com.jihe.punchnet.sdlan.network.maskIPToDigit
|
||||||
import com.jihe.punchnet.sdlan.network.run_sdlan
|
import com.jihe.punchnet.sdlan.network.run_sdlan
|
||||||
|
import com.jihe.punchnet.sdlan.utils.ipToString
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
@ -34,10 +39,13 @@ import java.io.FileOutputStream
|
|||||||
|
|
||||||
class PunchnetService : VpnService() , IfaceTun {
|
class PunchnetService : VpnService() , IfaceTun {
|
||||||
private val TAG = "PunchnetService"
|
private val TAG = "PunchnetService"
|
||||||
private val scope = CoroutineScope(Dispatchers.Default)
|
private val scope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
|
private val routes: MutableList<RouteItem> = mutableListOf()
|
||||||
|
|
||||||
private var vpnDescriptor: ParcelFileDescriptor? = null
|
private var vpnDescriptor: ParcelFileDescriptor? = null
|
||||||
|
|
||||||
|
|
||||||
var input: FileInputStream? = null
|
var input: FileInputStream? = null
|
||||||
var output: FileOutputStream? = null
|
var output: FileOutputStream? = null
|
||||||
|
|
||||||
@ -91,10 +99,16 @@ class PunchnetService : VpnService() , IfaceTun {
|
|||||||
}
|
}
|
||||||
vpnDescriptor?.close()
|
vpnDescriptor?.close()
|
||||||
|
|
||||||
vpnDescriptor = Builder()
|
var tempVpnDescriptor = Builder()
|
||||||
.setMtu(config.mtu)
|
.setMtu(config.mtu)
|
||||||
.addAddress(ip, config.ip.netBitLen.toInt())
|
.addAddress(ip, config.ip.netBitLen.toInt())
|
||||||
.setBlocking(true)
|
.setBlocking(true)
|
||||||
|
|
||||||
|
for (route in routes) {
|
||||||
|
tempVpnDescriptor = tempVpnDescriptor.addRoute(ipToString(route.net_ip), maskIPToDigit(route.mask_ip))
|
||||||
|
}
|
||||||
|
|
||||||
|
vpnDescriptor = tempVpnDescriptor
|
||||||
.establish()
|
.establish()
|
||||||
|
|
||||||
input = FileInputStream(vpnDescriptor!!.fileDescriptor)
|
input = FileInputStream(vpnDescriptor!!.fileDescriptor)
|
||||||
@ -107,7 +121,7 @@ class PunchnetService : VpnService() , IfaceTun {
|
|||||||
output?.close()
|
output?.close()
|
||||||
vpnDescriptor?.close()
|
vpnDescriptor?.close()
|
||||||
|
|
||||||
// stopForeground(true)
|
stopForeground(true)
|
||||||
stopSelf()
|
stopSelf()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,12 +135,17 @@ class PunchnetService : VpnService() , IfaceTun {
|
|||||||
Log.d("DIR", "filesdir = ${this.filesDir.path}")
|
Log.d("DIR", "filesdir = ${this.filesDir.path}")
|
||||||
|
|
||||||
arpTable.routeTable.clearRoute()
|
arpTable.routeTable.clearRoute()
|
||||||
|
routes.clear()
|
||||||
for (route in startArg?.routes?.toList()?:listOf()) {
|
for (route in startArg?.routes?.toList()?:listOf()) {
|
||||||
val r = cidrToRouteDetail(route.targetNetCIDR, route.gateway)
|
val r = RouteDetail(
|
||||||
if (r != null) {
|
mask = route.mask_ip,
|
||||||
|
gw = route.gateway,
|
||||||
|
maskedAddr = route.net_ip,
|
||||||
|
)
|
||||||
|
routes.add(route)
|
||||||
|
// val r = cidrToRouteDetail(route.targetNetCIDR, route.gateway)
|
||||||
arpTable.routeTable.addRoute(r)
|
arpTable.routeTable.addRoute(r)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val argument = Arguments(
|
val argument = Arguments(
|
||||||
baseDir = this.filesDir.path,
|
baseDir = this.filesDir.path,
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package com.jihe.punchnet
|
|||||||
|
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import com.jihe.punchnet.data.RouteItem
|
||||||
|
|
||||||
data class RouteInfo(
|
data class RouteInfo(
|
||||||
// 192.168.80.0/24
|
// 192.168.80.0/24
|
||||||
@ -12,14 +13,16 @@ data class RouteInfo(
|
|||||||
|
|
||||||
data class PunchnetServiceArgument(
|
data class PunchnetServiceArgument(
|
||||||
val token: String,
|
val token: String,
|
||||||
val routes: Array<RouteInfo>,
|
val routes: Array<RouteItem>,
|
||||||
): Parcelable {
|
): Parcelable {
|
||||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||||
dest.writeString(token)
|
dest.writeString(token)
|
||||||
dest.writeInt(routes.size)
|
dest.writeInt(routes.size)
|
||||||
routes.forEach { item ->
|
routes.forEach { item ->
|
||||||
dest.writeString(item.targetNetCIDR)
|
dest.writeLong(item.id)
|
||||||
dest.writeString(item.gateway)
|
dest.writeInt(item.net_ip)
|
||||||
|
dest.writeInt(item.mask_ip)
|
||||||
|
dest.writeInt(item.gateway)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,13 +34,18 @@ data class PunchnetServiceArgument(
|
|||||||
override fun createFromParcel(source: Parcel): PunchnetServiceArgument {
|
override fun createFromParcel(source: Parcel): PunchnetServiceArgument {
|
||||||
val token = source.readString()!!
|
val token = source.readString()!!
|
||||||
val size = source.readInt()
|
val size = source.readInt()
|
||||||
var routes = mutableListOf<RouteInfo>()
|
var routes = mutableListOf<RouteItem>()
|
||||||
for (i in 0..<size) {
|
for (i in 0..<size) {
|
||||||
val net = source.readString()!!
|
val id = source.readLong()!!
|
||||||
val gw = source.readString()!!
|
val netIP = source.readInt()!!
|
||||||
routes.add(RouteInfo(
|
val maskIP = source.readInt()!!
|
||||||
targetNetCIDR = net,
|
val gateway = source.readInt()!!
|
||||||
gateway = gw,
|
|
||||||
|
routes.add(RouteItem(
|
||||||
|
id = id,
|
||||||
|
net_ip = netIP,
|
||||||
|
mask_ip = maskIP,
|
||||||
|
gateway = gateway,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
return PunchnetServiceArgument(token, routes.toTypedArray())
|
return PunchnetServiceArgument(token, routes.toTypedArray())
|
||||||
|
|||||||
@ -16,44 +16,34 @@ import androidx.compose.runtime.State
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.core.content.ContextCompat.startForegroundService
|
import androidx.core.content.ContextCompat.startForegroundService
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.jihe.punchnet.PunchnetService
|
import com.jihe.punchnet.PunchnetService
|
||||||
import com.jihe.punchnet.PunchnetServiceArgument
|
import com.jihe.punchnet.PunchnetServiceArgument
|
||||||
import com.jihe.punchnet.RouteInfo
|
import com.jihe.punchnet.RouteInfo
|
||||||
|
import com.jihe.punchnet.sdlan.utils.ipToString
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.forEach
|
||||||
|
import kotlinx.coroutines.flow.toList
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class ServiceViewModel(application: Application): AndroidViewModel(application) {
|
class ServiceViewModel(application: Application): AndroidViewModel(application) {
|
||||||
private val _isRunning = mutableStateOf(false)
|
private val _isRunning = mutableStateOf(false)
|
||||||
val isRunning: State<Boolean> = _isRunning
|
val isRunning: State<Boolean> = _isRunning
|
||||||
|
|
||||||
private val _hasVpnPermission = mutableStateOf(false)
|
fun startService(context: Context, routes: Array<RouteItem>) {
|
||||||
val hasVpnPermission: State<Boolean> = _hasVpnPermission
|
// should has the permission
|
||||||
|
startVpnService(context, routes)
|
||||||
fun setVPNPermission(isGranted: Boolean) {
|
|
||||||
_hasVpnPermission.value = isGranted
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startService(context: Context, noPermissionCallback: (Intent)->Unit) {
|
|
||||||
val intent = VpnService.prepare(context)
|
|
||||||
if (intent != null) {
|
|
||||||
noPermissionCallback(intent)
|
|
||||||
} else {
|
|
||||||
startVpnService(context)
|
|
||||||
_isRunning.value = true
|
_isRunning.value = true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun startVpnService(context: Context) {
|
private fun startVpnService(context: Context, routes: Array<RouteItem>) {
|
||||||
val intent = Intent(context, PunchnetService::class.java)
|
val intent = Intent(context, PunchnetService::class.java)
|
||||||
intent.putExtra("argument", PunchnetServiceArgument(
|
intent.putExtra("argument", PunchnetServiceArgument(
|
||||||
"",
|
"",
|
||||||
arrayOf(
|
routes,
|
||||||
RouteInfo(
|
))
|
||||||
"192.168.80.0/24", "10.211.188.2"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
context.startForegroundService(intent)
|
context.startForegroundService(intent)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -1,6 +1,80 @@
|
|||||||
package com.jihe.punchnet.helper
|
package com.jihe.punchnet.helper
|
||||||
|
|
||||||
|
import com.jihe.punchnet.data.RouteItem
|
||||||
|
import com.jihe.punchnet.sdlan.network.maskDigitToInt
|
||||||
|
|
||||||
sealed class Screen(val route: String) {
|
sealed class Screen(val route: String) {
|
||||||
object ScreenRoutes: Screen("routes")
|
object ScreenRoutes: Screen("routes")
|
||||||
object ScreenMain: Screen("main")
|
object ScreenMain: Screen("main")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun IntToIPString(ip: Int): String {
|
||||||
|
val one = ((ip ushr 24) and 0x000000ff)
|
||||||
|
val two = ((ip ushr 16) and 0x000000ff)
|
||||||
|
val three = ((ip ushr 8) and 0x000000ff)
|
||||||
|
val four = ((ip) and 0x000000ff)
|
||||||
|
return "${one}.${two}.${three}.${four}"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun IPStringToInt(ipStr: String): Int? {
|
||||||
|
val digits = ipStr.split('.').filter{
|
||||||
|
val single = try {
|
||||||
|
it.toInt()
|
||||||
|
} catch(e: Exception) {
|
||||||
|
return@filter false
|
||||||
|
}
|
||||||
|
single in 0..255
|
||||||
|
}
|
||||||
|
if (digits.size != 4) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val result: Int
|
||||||
|
try {
|
||||||
|
result = (digits[0].toInt() shl 24) +
|
||||||
|
(digits[1].toInt() shl 16) +
|
||||||
|
(digits[2].toInt() shl 8) +
|
||||||
|
(digits[3].toInt())
|
||||||
|
} catch(e: Exception) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseCIDRAndGW(cidr: String, gw: String): RouteItem? {
|
||||||
|
val cidrSplit = cidr.split('/')
|
||||||
|
var ip = 0
|
||||||
|
var digit = 0
|
||||||
|
|
||||||
|
if (cidrSplit.size == 1) {
|
||||||
|
digit = 32
|
||||||
|
} else if (cidrSplit.size == 2) {
|
||||||
|
try {
|
||||||
|
val k = cidrSplit[1].toInt()
|
||||||
|
if (k in 1..32) {
|
||||||
|
digit = k
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val ipvalue = IPStringToInt(cidrSplit[0])
|
||||||
|
val gateway = IPStringToInt(gw)
|
||||||
|
if (ipvalue == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (gateway == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val mask = maskDigitToInt(digit)!!
|
||||||
|
return RouteItem(
|
||||||
|
net_ip = ipvalue,
|
||||||
|
gateway = gateway,
|
||||||
|
mask_ip = mask,
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -54,12 +54,22 @@ fun AppNavHost2(
|
|||||||
}
|
}
|
||||||
composable(Screen.RouteScreen.route) {
|
composable(Screen.RouteScreen.route) {
|
||||||
CustomHeaderScreen(
|
CustomHeaderScreen(
|
||||||
"光强计"
|
"路由设置"
|
||||||
) {
|
) {
|
||||||
|
RouteScreen(!serviceViewModel.isRunning.value, routeViewModel)
|
||||||
// LightScreen()
|
// LightScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
composable(Screen.ProfileScreen.route) {
|
composable(Screen.ProfileScreen.route) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"TODO",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
)
|
||||||
|
}
|
||||||
// Profile(dbdao, navController)
|
// Profile(dbdao, navController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import android.app.Activity.RESULT_CANCELED
|
|||||||
import android.app.Activity.RESULT_OK
|
import android.app.Activity.RESULT_OK
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.VpnService
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
@ -25,6 +26,7 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@ -46,17 +48,18 @@ fun HomeScreen(
|
|||||||
// started: MutableState<Boolean>,
|
// started: MutableState<Boolean>,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val allroutes = routeViewModel.allRoutes.collectAsState(emptyList())
|
||||||
val vpnLauncher = rememberLauncherForActivityResult(
|
val vpnLauncher = rememberLauncherForActivityResult(
|
||||||
contract = ActivityResultContracts.StartActivityForResult()
|
contract = ActivityResultContracts.StartActivityForResult()
|
||||||
) {result ->
|
) {result ->
|
||||||
when(result.resultCode) {
|
when(result.resultCode) {
|
||||||
RESULT_OK -> {
|
RESULT_OK -> {
|
||||||
serviceViewModel.setVPNPermission(true)
|
// serviceViewModel.startService(context)
|
||||||
|
// serviceViewModel.setVPNPermission(true)
|
||||||
|
serviceViewModel.startService(context, allroutes.value.toTypedArray())
|
||||||
}
|
}
|
||||||
RESULT_CANCELED -> {
|
RESULT_CANCELED -> {
|
||||||
serviceViewModel.setVPNPermission(false)
|
|
||||||
Toast.makeText(context, "vpn permission denied", Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, "vpn permission denied", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -115,12 +118,12 @@ fun HomeScreen(
|
|||||||
// if is running, should stop service
|
// if is running, should stop service
|
||||||
serviceViewModel.stopVpnService(context)
|
serviceViewModel.stopVpnService(context)
|
||||||
} else {
|
} else {
|
||||||
serviceViewModel.startService(
|
val intent = VpnService.prepare(context)
|
||||||
context,
|
if (intent != null) {
|
||||||
{intent->
|
|
||||||
vpnLauncher.launch(intent)
|
vpnLauncher.launch(intent)
|
||||||
},
|
} else {
|
||||||
)
|
serviceViewModel.startService(context, allroutes.value.toTypedArray())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// started.value = !started.value
|
// started.value = !started.value
|
||||||
},
|
},
|
||||||
@ -142,23 +145,3 @@ fun HomeScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun startVpnService(context: Context) {
|
|
||||||
val intent = Intent(context, PunchnetService::class.java)
|
|
||||||
|
|
||||||
intent.putExtra("argument", PunchnetServiceArgument(
|
|
||||||
"",
|
|
||||||
arrayOf(
|
|
||||||
RouteInfo(
|
|
||||||
"192.168.80.0/24", "10.211.188.2"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
context.startForegroundService(intent)
|
|
||||||
} else {
|
|
||||||
context.startService(intent)
|
|
||||||
}
|
|
||||||
Toast.makeText(context, "VPN service started", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
|
|||||||
211
app/src/main/java/com/jihe/punchnet/screen/RouteScreen.kt
Normal file
211
app/src/main/java/com/jihe/punchnet/screen/RouteScreen.kt
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
package com.jihe.punchnet.screen
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.collection.emptyLongSet
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Clear
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.RectangleShape
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.room.util.TableInfo
|
||||||
|
import com.jihe.punchnet.data.RouteItem
|
||||||
|
import com.jihe.punchnet.data.RouteViewModel
|
||||||
|
import com.jihe.punchnet.helper.IntToIPString
|
||||||
|
import com.jihe.punchnet.helper.parseCIDRAndGW
|
||||||
|
import com.jihe.punchnet.sdlan.network.maskIPToDigit
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AddOrModifyRoute(
|
||||||
|
routeViewModel: RouteViewModel,
|
||||||
|
initRoute: MutableState<RouteItem?>,
|
||||||
|
shouldShowRoute: MutableState<Boolean>,
|
||||||
|
|
||||||
|
) {
|
||||||
|
if (shouldShowRoute.value) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
var cidr by remember {
|
||||||
|
mutableStateOf(
|
||||||
|
if (initRoute.value == null)
|
||||||
|
""
|
||||||
|
else
|
||||||
|
""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var gw by remember {
|
||||||
|
mutableStateOf(
|
||||||
|
if (initRoute.value == null)
|
||||||
|
""
|
||||||
|
else
|
||||||
|
""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
shouldShowRoute.value = false
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
val item = parseCIDRAndGW(cidr, gw)
|
||||||
|
if (item == null) {
|
||||||
|
Toast.makeText(context, "数据解析出错", Toast.LENGTH_SHORT).show()
|
||||||
|
} else {
|
||||||
|
routeViewModel.insert(item)
|
||||||
|
shouldShowRoute.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("确定")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
"添加新路由"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Column {
|
||||||
|
TextField(
|
||||||
|
value = cidr,
|
||||||
|
onValueChange = { newValue ->
|
||||||
|
cidr = newValue
|
||||||
|
},
|
||||||
|
placeholder = {
|
||||||
|
Text(
|
||||||
|
"192.168.0.0/24",
|
||||||
|
color = MaterialTheme.colorScheme.onBackground.copy(.5f)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text("CIDR")
|
||||||
|
},
|
||||||
|
modifier = Modifier.padding(start = 16.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
TextField(
|
||||||
|
value = gw,
|
||||||
|
onValueChange = { newValue ->
|
||||||
|
gw = newValue
|
||||||
|
},
|
||||||
|
placeholder = {
|
||||||
|
Text(
|
||||||
|
"10.167.69.13",
|
||||||
|
color = MaterialTheme.colorScheme.onBackground.copy(.5f)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text("gateway")
|
||||||
|
},
|
||||||
|
modifier = Modifier.padding(start = 16.dp, top = 8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RouteScreen(
|
||||||
|
enabled: Boolean,
|
||||||
|
routeViewModel: RouteViewModel,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val showAddRoute: MutableState<RouteItem?> = remember { mutableStateOf(null) }
|
||||||
|
val shouldShow: MutableState<Boolean> = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
AddOrModifyRoute(routeViewModel, showAddRoute, shouldShow)
|
||||||
|
|
||||||
|
val routeItems by routeViewModel.allRoutes.collectAsState(initial = emptyList())
|
||||||
|
LazyColumn {
|
||||||
|
item {
|
||||||
|
Row(
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items(routeItems) {item ->
|
||||||
|
RouteCard(routeViewModel, item, enabled)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Button(
|
||||||
|
enabled = enabled,
|
||||||
|
shape = RectangleShape,
|
||||||
|
onClick = {
|
||||||
|
shouldShow.value = true
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text("添加路由")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RouteCard(
|
||||||
|
routeViewModel: RouteViewModel,
|
||||||
|
item: RouteItem,
|
||||||
|
enabled: Boolean,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Card (
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
|
||||||
|
){
|
||||||
|
Row (
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.height(36.dp)
|
||||||
|
){
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Clear,
|
||||||
|
contentDescription = "delete route",
|
||||||
|
modifier = Modifier.size(30.dp).clickable(
|
||||||
|
enabled = enabled,
|
||||||
|
) {
|
||||||
|
routeViewModel.deleteById(item.id)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"${IntToIPString(item.net_ip)}/${maskIPToDigit(item.mask_ip)}",
|
||||||
|
modifier = Modifier.padding(start = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
IntToIPString(item.gateway),
|
||||||
|
modifier = Modifier.padding(start = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -45,11 +45,21 @@ fun cidrToRouteDetail(cidr: String, gw: String): RouteDetail? {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun maskIPToDigit(mask: Int): Int {
|
||||||
|
var count = 0
|
||||||
|
var temp = mask
|
||||||
|
while (temp != 0) {
|
||||||
|
temp = temp shl 1
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
fun maskDigitToInt(digit: Int): Int? {
|
fun maskDigitToInt(digit: Int): Int? {
|
||||||
if (digit <= 0 || digit >= 32) {
|
if (digit <= 0 || digit >= 32) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return ((2 shl (32-digit)) - 1)
|
return ((1 shl (32-digit)) - 1).inv()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ipStringToInt(ip: String): Int? {
|
fun ipStringToInt(ip: String): Int? {
|
||||||
|
|||||||
BIN
key-store/punchnet.jks
Normal file
BIN
key-store/punchnet.jks
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user