排查二次启动的问题
This commit is contained in:
parent
867fb5ca72
commit
87bee05140
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,5 +1,6 @@
|
||||
package com.jihe.punchnet
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.VpnService
|
||||
@ -31,12 +32,21 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.jihe.punchnet.data.ButtonState
|
||||
import com.jihe.punchnet.data.ButtonViewModel
|
||||
import com.jihe.punchnet.data.RouteViewModel
|
||||
import com.jihe.punchnet.data.ServiceViewModel
|
||||
import com.jihe.punchnet.screen.MainApp
|
||||
import com.jihe.punchnet.ui.theme.PunchnetTheme
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
class PunchnetApp: Application() {
|
||||
val buttonModel: ButtonViewModel by lazy {
|
||||
ViewModelProvider.AndroidViewModelFactory.getInstance(this)
|
||||
.create(ButtonViewModel::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
private val TAG = "MainActivity"
|
||||
|
||||
@ -58,6 +68,18 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
val buttonModel: ButtonViewModel by viewModels {
|
||||
object: ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun<T: ViewModel> create(modelClass: Class<T>): T {
|
||||
return ButtonViewModel(application) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*
|
||||
fun prepareAndStartVPN() {
|
||||
@ -97,9 +119,12 @@ class MainActivity : ComponentActivity() {
|
||||
Log.d("DIR", "filesdir = ${this.filesDir}")
|
||||
|
||||
|
||||
val buttonModel = ButtonViewModel()
|
||||
buttonModel.updateState(ButtonState.ButtonStopped)
|
||||
|
||||
setContent {
|
||||
PunchnetTheme {
|
||||
MainApp(serviceModel, viewModel, rememberNavController())
|
||||
MainApp(buttonModel, serviceModel, viewModel, rememberNavController())
|
||||
/*
|
||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||
StartStop(
|
||||
|
||||
@ -13,6 +13,9 @@ import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.jihe.punchnet.data.ButtonRepository
|
||||
import com.jihe.punchnet.data.ButtonState
|
||||
import com.jihe.punchnet.data.ButtonViewModel
|
||||
import com.jihe.punchnet.data.RouteItem
|
||||
import com.jihe.punchnet.data.RouteViewModel
|
||||
import com.jihe.punchnet.sdlan.config.Arguments
|
||||
@ -42,6 +45,7 @@ import java.io.FileOutputStream
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
class PunchnetService : VpnService() , IfaceTun {
|
||||
|
||||
private val TAG = "PunchnetService"
|
||||
private var scope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
@ -50,9 +54,7 @@ class PunchnetService : VpnService() , IfaceTun {
|
||||
private var vpnDescriptor: ParcelFileDescriptor? = null
|
||||
|
||||
private val serviceLock = Any()
|
||||
private val isRunning = AtomicBoolean(false)
|
||||
|
||||
private val start_stop_channel = Channel<StartStopChanInfo>(100)
|
||||
// private val isRunning = AtomicBoolean(false)
|
||||
|
||||
var input: FileInputStream? = null
|
||||
var output: FileOutputStream? = null
|
||||
@ -129,17 +131,24 @@ class PunchnetService : VpnService() , IfaceTun {
|
||||
}
|
||||
|
||||
vpnDescriptor = tempVpnDescriptor
|
||||
.establish()
|
||||
|
||||
input = FileInputStream(vpnDescriptor!!.fileDescriptor)
|
||||
output = FileOutputStream(vpnDescriptor!!.fileDescriptor)
|
||||
.establish()?.apply {
|
||||
input = FileInputStream(fileDescriptor)
|
||||
output = FileOutputStream(fileDescriptor)
|
||||
}
|
||||
ButtonRepository.updateState(ButtonState.ButtonStarted)
|
||||
}
|
||||
|
||||
private fun disconnect() {
|
||||
synchronized(serviceLock) {
|
||||
if (ButtonRepository.buttonState.value != ButtonState.ButtonStarted) {
|
||||
return
|
||||
}
|
||||
/*
|
||||
if (!isRunning.get()) {
|
||||
return
|
||||
}
|
||||
*/
|
||||
ButtonRepository.updateState(ButtonState.ButtonStopping)
|
||||
|
||||
Toast.makeText(this, "stop vpn called", Toast.LENGTH_LONG).show()
|
||||
input?.close()
|
||||
@ -149,15 +158,23 @@ class PunchnetService : VpnService() , IfaceTun {
|
||||
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
stopSelf()
|
||||
isRunning.set(false)
|
||||
// isRunning.set(false)
|
||||
}
|
||||
ButtonRepository.updateState(ButtonState.ButtonStopped)
|
||||
}
|
||||
|
||||
private fun connect(startArg: PunchnetServiceArgument?) {
|
||||
synchronized(serviceLock) {
|
||||
if (ButtonRepository.buttonState.value != ButtonState.ButtonStopped) {
|
||||
return
|
||||
}
|
||||
/*
|
||||
if (isRunning.get()) {
|
||||
return
|
||||
}
|
||||
*/
|
||||
|
||||
ButtonRepository.updateState(ButtonState.ButtonStarting)
|
||||
|
||||
val iface = this
|
||||
val server = "punchnet.aioe.tech"
|
||||
@ -194,11 +211,13 @@ class PunchnetService : VpnService() , IfaceTun {
|
||||
startForeground(1, notification)
|
||||
|
||||
scope.launch {
|
||||
run_sdlan(scope, start_stop_channel, iface, argument, startArg)
|
||||
run_sdlan(scope, iface, argument, startArg)
|
||||
}
|
||||
|
||||
isRunning.set(true)
|
||||
// isRunning.set(true)
|
||||
}
|
||||
// ButtonRepository.updateState(ButtonState.ButtonStarted)
|
||||
// buttonViewModel.changeButtonState(ButtonState.ButtonStarted)
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
@ -209,7 +228,6 @@ class PunchnetService : VpnService() , IfaceTun {
|
||||
disconnect()
|
||||
START_STICKY
|
||||
} else {
|
||||
|
||||
scope = CoroutineScope(Dispatchers.IO)
|
||||
arpTable = ARPTable(scope)
|
||||
|
||||
|
||||
34
app/src/main/java/com/jihe/punchnet/data/ButtonViewModel.kt
Normal file
34
app/src/main/java/com/jihe/punchnet/data/ButtonViewModel.kt
Normal file
@ -0,0 +1,34 @@
|
||||
package com.jihe.punchnet.data
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
object ButtonRepository {
|
||||
private val _buttonState: MutableStateFlow<ButtonState> = MutableStateFlow(ButtonState.ButtonStopped)
|
||||
val buttonState: StateFlow<ButtonState> = _buttonState
|
||||
|
||||
fun updateState(state: ButtonState) {
|
||||
_buttonState.value = state
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ButtonState(val text: String, val enabled: Boolean) {
|
||||
object ButtonStarted: ButtonState("停止", true)
|
||||
object ButtonStarting: ButtonState("启动中", false)
|
||||
object ButtonStopped: ButtonState("启动", true)
|
||||
object ButtonStopping: ButtonState("停止中", false)
|
||||
}
|
||||
|
||||
class ButtonViewModel(): ViewModel() {
|
||||
val buttonState = ButtonRepository.buttonState
|
||||
|
||||
fun updateState(state: ButtonState) {
|
||||
ButtonRepository.updateState(state)
|
||||
}
|
||||
}
|
||||
@ -27,20 +27,24 @@ import kotlinx.coroutines.flow.forEach
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
class ServiceViewModel(application: Application): AndroidViewModel(application) {
|
||||
/*
|
||||
private val _isRunning = mutableStateOf(false)
|
||||
val isRunning: State<Boolean> = _isRunning
|
||||
*/
|
||||
|
||||
fun startService(context: Context, routes: Array<RouteItem>) {
|
||||
fun startService(context: Context, routes: Array<RouteItem>, token: String) {
|
||||
// should has the permission
|
||||
startVpnService(context, routes)
|
||||
_isRunning.value = true
|
||||
startVpnService(context, routes, token)
|
||||
// ButtonRepository.updateState(ButtonState.ButtonStarting)
|
||||
// _isRunning.value = true
|
||||
}
|
||||
|
||||
private fun startVpnService(context: Context, routes: Array<RouteItem>) {
|
||||
private fun startVpnService(context: Context, routes: Array<RouteItem>, token: String) {
|
||||
val intent = Intent(context, PunchnetService::class.java)
|
||||
intent.putExtra("argument", PunchnetServiceArgument(
|
||||
"",
|
||||
token,
|
||||
routes,
|
||||
))
|
||||
|
||||
@ -59,7 +63,7 @@ class ServiceViewModel(application: Application): AndroidViewModel(application)
|
||||
PunchnetService.ACTION_DISCONNECT })
|
||||
// stopService(Intent(this, PunchnetService::class.java))
|
||||
Log.d("STOPPED PUNCHNET", "stopping PUNCHNET")
|
||||
_isRunning.value = false
|
||||
// ButtonRepository.updateState(ButtonState.ButtonStopping)
|
||||
// Toast.makeText(this, "VPN service stopped", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,48 @@
|
||||
package com.jihe.punchnet.helper
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import com.jihe.punchnet.data.RouteItem
|
||||
import com.jihe.punchnet.sdlan.network.maskDigitToInt
|
||||
|
||||
const val PreferenceRepositoryName = "pref"
|
||||
sealed class PreferenceName(val name: String) {
|
||||
object PreferenceToken: PreferenceName("token")
|
||||
}
|
||||
|
||||
fun getPreferenceString(context: Context, name: PreferenceName): String? {
|
||||
val preference = context.getSharedPreferences(PreferenceRepositoryName, Activity.MODE_PRIVATE)
|
||||
try {
|
||||
return preference.getString(name.name, "")
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun getPreferenceInt(context: Context, name: PreferenceName): Int? {
|
||||
val preference = context.getSharedPreferences(PreferenceRepositoryName, Activity.MODE_PRIVATE)
|
||||
try {
|
||||
return preference.getInt(name.name, 0)
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun setPreferenceString(context: Context, name: PreferenceName, value: String) {
|
||||
val preference = context.getSharedPreferences(PreferenceRepositoryName, Activity.MODE_PRIVATE)
|
||||
val editor = preference.edit()
|
||||
editor.putString(name.name, value)
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
fun setPreferenceInt(context: Context, name: PreferenceName, value: Int) {
|
||||
val preference = context.getSharedPreferences(PreferenceRepositoryName, Activity.MODE_PRIVATE)
|
||||
val editor = preference.edit()
|
||||
editor.putInt(name.name, value)
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
|
||||
sealed class Screen(val route: String) {
|
||||
object ScreenRoutes: Screen("routes")
|
||||
object ScreenMain: Screen("main")
|
||||
|
||||
@ -17,6 +17,7 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
@ -34,29 +35,34 @@ import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navArgument
|
||||
import com.jihe.punchnet.R
|
||||
import com.jihe.punchnet.RouteInfo
|
||||
import com.jihe.punchnet.data.ButtonRepository
|
||||
import com.jihe.punchnet.data.ButtonState
|
||||
import com.jihe.punchnet.data.ButtonViewModel
|
||||
import com.jihe.punchnet.data.RouteViewModel
|
||||
import com.jihe.punchnet.data.ServiceViewModel
|
||||
|
||||
@Composable
|
||||
fun AppNavHost2(
|
||||
buttonViewModel: ButtonViewModel,
|
||||
serviceViewModel: ServiceViewModel,
|
||||
routeViewModel: RouteViewModel = viewModel(),
|
||||
navController: NavHostController,
|
||||
paddingValues: PaddingValues
|
||||
) {
|
||||
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = Screen.MainScreen.route,
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
) {
|
||||
composable(Screen.MainScreen.route) {
|
||||
HomeScreen(serviceViewModel, routeViewModel)
|
||||
HomeScreen(buttonViewModel, serviceViewModel, routeViewModel)
|
||||
}
|
||||
composable(Screen.RouteScreen.route) {
|
||||
CustomHeaderScreen(
|
||||
"路由设置"
|
||||
) {
|
||||
RouteScreen(!serviceViewModel.isRunning.value, routeViewModel)
|
||||
RouteScreen(ButtonRepository.buttonState.collectAsState().value != ButtonState.ButtonStarted, routeViewModel)
|
||||
// LightScreen()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.jihe.punchnet.screen
|
||||
|
||||
import android.app.Activity.MODE_PRIVATE
|
||||
import android.app.Activity.RESULT_CANCELED
|
||||
import android.app.Activity.RESULT_OK
|
||||
import android.content.Context
|
||||
@ -9,8 +10,15 @@ import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.indication
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
@ -20,13 +28,22 @@ import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
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.platform.LocalContext
|
||||
@ -38,17 +55,116 @@ import com.jihe.punchnet.PunchnetService
|
||||
import com.jihe.punchnet.PunchnetServiceArgument
|
||||
import com.jihe.punchnet.R
|
||||
import com.jihe.punchnet.RouteInfo
|
||||
import com.jihe.punchnet.data.ButtonRepository
|
||||
import com.jihe.punchnet.data.ButtonState
|
||||
import com.jihe.punchnet.data.ButtonViewModel
|
||||
import com.jihe.punchnet.data.RouteViewModel
|
||||
import com.jihe.punchnet.data.ServiceViewModel
|
||||
import com.jihe.punchnet.helper.PreferenceName
|
||||
import com.jihe.punchnet.helper.getPreferenceString
|
||||
import com.jihe.punchnet.helper.setPreferenceString
|
||||
import kotlin.math.exp
|
||||
|
||||
data class HomeDropDownInfo(
|
||||
@DrawableRes val icon: Int,
|
||||
val name: String,
|
||||
val callback: (()->Unit)? = null,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun HomeDropdownMenu(
|
||||
showTokenDialog: MutableState<Boolean>,
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
// var showTokenDialog by remember { mutableStateOf(false) }
|
||||
// val showSettingDialog by remember { mutableStateOf(false) }
|
||||
|
||||
val context = LocalContext.current
|
||||
val homeItems = arrayOf(
|
||||
HomeDropDownInfo(
|
||||
R.drawable.tag,
|
||||
"修改token",
|
||||
{
|
||||
showTokenDialog.value = true
|
||||
}
|
||||
),
|
||||
/*
|
||||
HomeDropDownInfo(
|
||||
R.drawable.preferences,
|
||||
"修改云端配置"
|
||||
)
|
||||
*/
|
||||
)
|
||||
|
||||
|
||||
|
||||
Box() {
|
||||
IconButton(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
modifier = Modifier.indication(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
),
|
||||
onClick = {
|
||||
expanded = true
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Settings,
|
||||
contentDescription = "settings",
|
||||
)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = {expanded = !expanded}
|
||||
) {
|
||||
homeItems.forEach { item ->
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
expanded = false
|
||||
item.callback?.invoke()
|
||||
},
|
||||
text = {
|
||||
|
||||
Row (
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
){
|
||||
Icon(
|
||||
painter = painterResource(item.icon),
|
||||
modifier = Modifier.size(24.dp),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
|
||||
Spacer(
|
||||
modifier = Modifier.padding(start = 8.dp)
|
||||
)
|
||||
|
||||
Text(text=item.name)
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HomeScreen(
|
||||
buttonViewModel: ButtonViewModel,
|
||||
serviceViewModel: ServiceViewModel,
|
||||
routeViewModel: RouteViewModel,
|
||||
// started: MutableState<Boolean>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
||||
val context = LocalContext.current
|
||||
val tkPref = getPreferenceString(context, PreferenceName.PreferenceToken)
|
||||
|
||||
var tokenvalue by remember {mutableStateOf(tkPref?:"")}
|
||||
|
||||
val allroutes = routeViewModel.allRoutes.collectAsState(emptyList())
|
||||
val vpnLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.StartActivityForResult()
|
||||
@ -57,7 +173,9 @@ fun HomeScreen(
|
||||
RESULT_OK -> {
|
||||
// serviceViewModel.startService(context)
|
||||
// serviceViewModel.setVPNPermission(true)
|
||||
serviceViewModel.startService(context, allroutes.value.toTypedArray())
|
||||
val tk = getPreferenceString(context, PreferenceName.PreferenceToken)?:""
|
||||
println("now token value2 = '${tokenvalue}'")
|
||||
serviceViewModel.startService(context, allroutes.value.toTypedArray(), tk)
|
||||
}
|
||||
RESULT_CANCELED -> {
|
||||
Toast.makeText(context, "vpn permission denied", Toast.LENGTH_SHORT).show()
|
||||
@ -65,27 +183,54 @@ fun HomeScreen(
|
||||
}
|
||||
}
|
||||
|
||||
val buttonState = buttonViewModel.buttonState.collectAsState()
|
||||
val showTokenDialog = remember { mutableStateOf(false) }
|
||||
|
||||
|
||||
|
||||
CustomHeaderScreen(
|
||||
"",
|
||||
onBack = null,
|
||||
onMenu = {
|
||||
Icon(
|
||||
Icons.Default.Settings,
|
||||
contentDescription = "settings",
|
||||
modifier = Modifier.fillMaxHeight()
|
||||
)
|
||||
// NavListMenu(navController, dbdao)
|
||||
/*
|
||||
navController.navigate(
|
||||
Screen.ActionDetail.route.replace(
|
||||
"{action_name}",
|
||||
""
|
||||
)
|
||||
)
|
||||
*/
|
||||
|
||||
HomeDropdownMenu(showTokenDialog)
|
||||
}
|
||||
) {
|
||||
if (showTokenDialog.value) {
|
||||
AlertDialog(
|
||||
onDismissRequest = {showTokenDialog.value=false},
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = {
|
||||
setPreferenceString(context, PreferenceName.PreferenceToken, tokenvalue)
|
||||
println("now token value = '${tokenvalue}'")
|
||||
showTokenDialog.value = false
|
||||
}
|
||||
) {
|
||||
Text("确定")
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text("修改token")
|
||||
},
|
||||
text = {
|
||||
TextField(
|
||||
value = tokenvalue,
|
||||
onValueChange = { newValue ->
|
||||
tokenvalue = newValue.trim()
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.tag),
|
||||
contentDescription = "token",
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
.padding(top=50.dp),
|
||||
@ -114,30 +259,38 @@ fun HomeScreen(
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
if (serviceViewModel.isRunning.value) {
|
||||
if (buttonViewModel.buttonState.value == ButtonState.ButtonStarted) {
|
||||
// if (serviceViewModel.isRunning.value) {
|
||||
// if is running, should stop service
|
||||
serviceViewModel.stopVpnService(context)
|
||||
} else {
|
||||
} else if (buttonViewModel.buttonState.value == ButtonState.ButtonStopped){
|
||||
val intent = VpnService.prepare(context)
|
||||
if (intent != null) {
|
||||
vpnLauncher.launch(intent)
|
||||
} else {
|
||||
serviceViewModel.startService(context, allroutes.value.toTypedArray())
|
||||
val tk = getPreferenceString(context, PreferenceName.PreferenceToken)?:""
|
||||
println("now token value1 = '${tokenvalue}'")
|
||||
serviceViewModel.startService(context, allroutes.value.toTypedArray(), tk)
|
||||
}
|
||||
}
|
||||
// started.value = !started.value
|
||||
},
|
||||
enabled = buttonState.value.enabled,
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
modifier = Modifier.padding(top=48.dp)
|
||||
.width(120.dp)
|
||||
.height(40.dp)
|
||||
) {
|
||||
Text(
|
||||
buttonState.value.text
|
||||
// buttonViewModel.buttonText.value
|
||||
/*
|
||||
if (serviceViewModel.isRunning.value) {
|
||||
"停止"
|
||||
} else {
|
||||
"启动"
|
||||
}
|
||||
*/
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import com.jihe.punchnet.data.ButtonViewModel
|
||||
import com.jihe.punchnet.data.RouteViewModel
|
||||
import com.jihe.punchnet.data.ServiceViewModel
|
||||
|
||||
@ -37,6 +38,7 @@ val bottomNavItems = listOf(
|
||||
|
||||
@Composable
|
||||
fun MainApp(
|
||||
buttonViewModel: ButtonViewModel = viewModel(),
|
||||
serviceViewModel: ServiceViewModel = viewModel(),
|
||||
routeModel: RouteViewModel = viewModel(),
|
||||
navController: NavHostController,
|
||||
@ -54,7 +56,7 @@ fun MainApp(
|
||||
modifier = Modifier.padding(paddingValues).size(200.dp)
|
||||
)
|
||||
*/
|
||||
AppNavHost2(serviceViewModel, routeModel, navController, paddingValues)
|
||||
AppNavHost2(buttonViewModel, serviceViewModel, routeModel, navController, paddingValues)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,8 @@ package com.jihe.punchnet.sdlan.network
|
||||
|
||||
import android.util.Log
|
||||
import com.jihe.punchnet.PunchnetServiceArgument
|
||||
import com.jihe.punchnet.data.ButtonRepository
|
||||
import com.jihe.punchnet.data.ButtonState
|
||||
import com.jihe.punchnet.protobuf.PunchProto.SDLDevAddr
|
||||
import com.jihe.punchnet.protobuf.PunchProto.SDLRegisterSuper
|
||||
import com.jihe.punchnet.protobuf.PunchProto.SDLRegisterSuperAck
|
||||
@ -18,9 +20,12 @@ import com.jihe.punchnet.sdlan.utils.UniqueNodeID
|
||||
import com.jihe.punchnet.sdlan.utils.ipToString
|
||||
import com.jihe.punchnet.sdlan.utils.macToString
|
||||
import com.jihe.punchnet.sdlan.utils.parseScoketAddressV4FromString
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.ReceiveChannel
|
||||
@ -91,9 +96,11 @@ suspend fun onMessage(scope: CoroutineScope, data: SDLanTCP) {
|
||||
when(nakcode) {
|
||||
NakMsgCode.InvalidToken, NakMsgCode.NodeDisabled-> {
|
||||
node.startStopChannel.send(StartStopChanInfo(StartStopFlag.IsStop, null))
|
||||
exitProcess(-1)
|
||||
ButtonRepository.updateState(ButtonState.ButtonStopped)
|
||||
scope.cancel()
|
||||
}
|
||||
else -> {
|
||||
ButtonRepository.updateState(ButtonState.ButtonStopped)
|
||||
node.startStopChannel.send(StartStopChanInfo(StartStopFlag.IsStop, null))
|
||||
}
|
||||
}
|
||||
@ -131,10 +138,9 @@ suspend fun onMessage(scope: CoroutineScope, data: SDLanTCP) {
|
||||
println("error packet type: ${data.packetType.toUByte()}")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
suspend fun run_sdlan(scope: CoroutineScope, start_stop_channel: Channel<StartStopChanInfo>, iface: Iface, argument: Arguments, routeinfo: PunchnetServiceArgument?) {
|
||||
suspend fun run_sdlan(scope: CoroutineScope, iface: Iface, argument: Arguments, routeinfo: PunchnetServiceArgument?) {
|
||||
UniqueNodeID.setBaseDir(argument.baseDir)
|
||||
val edgeUUID = UniqueNodeID.getUUID()
|
||||
val config = parseConfig(edgeUUID, argument)
|
||||
@ -147,7 +153,7 @@ suspend fun run_sdlan(scope: CoroutineScope, start_stop_channel: Channel<StartSt
|
||||
|
||||
|
||||
val toSocket = Channel<ByteArray>(100)
|
||||
// val start_stop_channel = Channel<StartStopChanInfo>(100)
|
||||
val start_stop_channel = Channel<StartStopChanInfo>(100)
|
||||
initEdge(scope, iface, argument.token, config, toSocket, start_stop_channel)
|
||||
|
||||
val tcp = argument.tcp.split(":")
|
||||
@ -156,7 +162,6 @@ suspend fun run_sdlan(scope: CoroutineScope, start_stop_channel: Channel<StartSt
|
||||
|
||||
val node = Node.getInstance()
|
||||
|
||||
|
||||
val before = ubyteArrayOf(126u,162u,25u,63u,148u,147u,198u,41u,69u,165u,149u,101u,153u,82u,190u,21u,48u,120u,26u,64u,142u,103u,159u,60u,47u,129u,176u,17u,232u,210u,36u,56u)
|
||||
val encrypted = node.rsa.encrypt(before.toByteArray(), use_private_key = false)
|
||||
// println("encrypted: ${encrypted.contentToString()}")
|
||||
@ -227,6 +232,8 @@ suspend fun run_sdlan(scope: CoroutineScope, start_stop_channel: Channel<StartSt
|
||||
} catch (e: Exception) {
|
||||
println("iface read is cancelled")
|
||||
node.iface?.close()
|
||||
node.udpSockV4.close()
|
||||
node.udpSockMulticast?.close()
|
||||
}
|
||||
}
|
||||
/*
|
||||
@ -253,7 +260,6 @@ suspend fun run_sdlan(scope: CoroutineScope, start_stop_channel: Channel<StartSt
|
||||
|
||||
// CoroutineScope(Dispatchers.IO).async {
|
||||
scope.launch {
|
||||
try {
|
||||
initTCPConn(
|
||||
scope,
|
||||
tcp[0], tcp[1].toInt(),
|
||||
@ -268,13 +274,12 @@ suspend fun run_sdlan(scope: CoroutineScope, start_stop_channel: Channel<StartSt
|
||||
},
|
||||
null,
|
||||
)
|
||||
} finally {
|
||||
println("initTCPConn is cancelled")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
println("sending start")
|
||||
start_stop_channel.send(StartStopChanInfo(StartStopFlag.IsStart, null))
|
||||
println("sent start")
|
||||
|
||||
val cancel = Channel<Boolean>(100)
|
||||
runEdgeLoop(scope, node, cancel)
|
||||
@ -295,16 +300,23 @@ suspend fun runEdgeLoop(scope: CoroutineScope, node: Node, cancel: ReceiveChanne
|
||||
|
||||
suspend fun loopSocketV4(scope: CoroutineScope, node: Node, sock: SDLanSocket, cancel: ReceiveChannel<Boolean>) {
|
||||
val job_stun_request = scope.async {
|
||||
try {
|
||||
while(true) {
|
||||
delay(10_000)
|
||||
node.sendStunRequest()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
val job_handle_packet = scope.async {
|
||||
try {
|
||||
while(true) {
|
||||
readAndParsePacket(scope, node, sock)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
sock.close()
|
||||
}
|
||||
}
|
||||
|
||||
select<Unit> {
|
||||
@ -435,15 +447,17 @@ suspend fun initTCPConn(
|
||||
onDisconnected: suspend ()->Unit,
|
||||
connectingChan: SendChannel<ConnectingState>?
|
||||
) {
|
||||
|
||||
val started = AtomicBoolean(false)
|
||||
// var started: Boolean = false
|
||||
var startPktID: Int? = null
|
||||
val node = Node.getInstance()
|
||||
|
||||
while(true) {
|
||||
while (true) {
|
||||
connectingChan?.send(ConnectingState.NotConnected)
|
||||
|
||||
if (!started.get()) {
|
||||
while(true) {
|
||||
while (true) {
|
||||
println("waiting for start_stop")
|
||||
val startStopInfo = start_stop.receive()
|
||||
if (startStopInfo.flag == StartStopFlag.IsStart) {
|
||||
@ -459,7 +473,7 @@ suspend fun initTCPConn(
|
||||
}
|
||||
|
||||
connectingChan?.send(ConnectingState.Connecting)
|
||||
TerminalLogger.debugf {"try connecting tcp..."}
|
||||
TerminalLogger.debugf { "try connecting tcp..." }
|
||||
|
||||
val socket: Socket
|
||||
try {
|
||||
@ -467,12 +481,18 @@ suspend fun initTCPConn(
|
||||
TerminalLogger.debugf { "connecting to $tcpHost:$tcpPort" }
|
||||
socket = Socket(tcpHost, tcpPort)
|
||||
}
|
||||
}catch(e: CancellationException) {
|
||||
withContext(NonCancellable) {
|
||||
node.iface?.close()
|
||||
}
|
||||
return
|
||||
} catch (e: Exception) {
|
||||
TerminalLogger.errorf { "failed to connect to ${tcpHost}:${tcpPort}: $e"}
|
||||
TerminalLogger.errorf { "failed to connect to ${tcpHost}:${tcpPort}: $e" }
|
||||
delay(3000)
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
val node = Node.getInstance()
|
||||
val outIP = ByteBuffer.wrap(socket.localAddress.address).getInt()
|
||||
node.outerIPV4.set(outIP)
|
||||
@ -482,17 +502,17 @@ suspend fun initTCPConn(
|
||||
val input = DataInputStream(socket.getInputStream())
|
||||
try {
|
||||
println("job read packet starts")
|
||||
while(true) {
|
||||
while (true) {
|
||||
val tcpPacket = readPacket(input)
|
||||
if (tcpPacket == null) {
|
||||
TerminalLogger.errorf {"tcp Packet is null"}
|
||||
TerminalLogger.errorf { "tcp Packet is null" }
|
||||
break
|
||||
}
|
||||
onMessage(scope, tcpPacket)
|
||||
}
|
||||
} finally {
|
||||
TerminalLogger.errorf {"input closing"}
|
||||
input.close()
|
||||
TerminalLogger.errorf { "input closing" }
|
||||
}
|
||||
}
|
||||
|
||||
@ -500,15 +520,15 @@ suspend fun initTCPConn(
|
||||
val job_write_to_packet = scope.async {
|
||||
val output = DataOutputStream(socket.getOutputStream())
|
||||
try {
|
||||
TerminalLogger.debugf {"job write to packet starts"}
|
||||
while(true) {
|
||||
TerminalLogger.debugf { "job write to packet starts" }
|
||||
while (true) {
|
||||
try {
|
||||
val msg = toSocket.receive()
|
||||
TerminalLogger.debugf{"received message"}
|
||||
TerminalLogger.debugf { "received message" }
|
||||
output.write(msg)
|
||||
} catch (e: Exception) {
|
||||
output.close()
|
||||
TerminalLogger.errorf {"failed to receive message to tcp: $e"}
|
||||
TerminalLogger.errorf { "failed to receive message to tcp: $e" }
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -520,11 +540,11 @@ suspend fun initTCPConn(
|
||||
// val job_check_pong = CoroutineScope(Dispatchers.IO).async {
|
||||
val job_check_pong = scope.async {
|
||||
println("job check pong starts")
|
||||
while(true) {
|
||||
while (true) {
|
||||
delay(10_000)
|
||||
val now = System.currentTimeMillis()/1000
|
||||
if (connected.get() && (now-pong_time.get()>SDLanConfig.TCP_PING_TIME*2)) {
|
||||
TerminalLogger.errorf {"tcp pong check expired"}
|
||||
val now = System.currentTimeMillis() / 1000
|
||||
if (connected.get() && (now - pong_time.get() > SDLanConfig.TCP_PING_TIME * 2)) {
|
||||
TerminalLogger.errorf { "tcp pong check expired" }
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -533,15 +553,15 @@ suspend fun initTCPConn(
|
||||
// val job_check_stop = CoroutineScope(Dispatchers.IO).async {
|
||||
val job_check_stop = scope.async {
|
||||
println("job check stop starts")
|
||||
while(true) {
|
||||
while (true) {
|
||||
try {
|
||||
val v = start_stop.receive()
|
||||
if (v.flag == StartStopFlag.IsStop) {
|
||||
started.set(false)
|
||||
break
|
||||
}
|
||||
} catch(e: Exception) {
|
||||
started.set(false)
|
||||
} catch (e: Exception) {
|
||||
// started.set(false)
|
||||
break
|
||||
}
|
||||
|
||||
@ -579,6 +599,12 @@ suspend fun initTCPConn(
|
||||
println("m5")
|
||||
|
||||
delay(1000)
|
||||
} catch (e: Exception) {
|
||||
withContext(NonCancellable) {
|
||||
node.iface?.close()
|
||||
socket.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -23,8 +23,6 @@ class SDLanSocket(val scope: CoroutineScope, val addr: String, val port: Int, va
|
||||
sock
|
||||
}
|
||||
|
||||
private var job: Job? = null
|
||||
|
||||
fun loop(): ReceiveChannel<DatagramPacket> {
|
||||
val channel = Channel<DatagramPacket>(100)
|
||||
scope.launch {
|
||||
@ -44,7 +42,7 @@ class SDLanSocket(val scope: CoroutineScope, val addr: String, val port: Int, va
|
||||
connection.send(packet)
|
||||
}
|
||||
} catch(e: Exception) {
|
||||
TerminalLogger.errorf {"Failed to send to: $e"}
|
||||
TerminalLogger.errorf {"Failed to send to: $e, localport: ${connection.localPort}"}
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,7 +57,6 @@ class SDLanSocket(val scope: CoroutineScope, val addr: String, val port: Int, va
|
||||
}
|
||||
|
||||
suspend fun close() {
|
||||
job?.cancelAndJoin()
|
||||
connection.close()
|
||||
}
|
||||
}
|
||||
|
||||
9
app/src/main/res/drawable/preferences.xml
Normal file
9
app/src/main/res/drawable/preferences.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="200dp"
|
||||
android:height="200dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M802.1,102.4L222.2,102.4C156.3,102.4 102.4,156 102.4,221.9v579.9A120.1,120.1 0,0 0,222.2 921.6L802.1,921.6c65.9,0 119.5,-53.9 119.5,-119.8L921.6,221.9C921.6,156 868,102.4 802.1,102.4zM751.3,767.7h-297v11.6c0,19.1 -15.4,34.1 -34.1,34.1s-34.1,-15 -34.1,-34.1v-11.6h-112.6c-19.1,0 -34.1,-15.4 -34.1,-34.1s15,-34.1 34.1,-34.1h112.6v-11.6c0,-18.8 15.4,-34.1 34.1,-34.1s34.1,15.4 34.1,34.1v11.6h297c18.8,0 34.1,15.4 34.1,34.1s-15.4,34.1 -34.1,34.1zM751.3,543.4h-123.9v11.6c0,18.8 -15.4,34.1 -34.1,34.1s-34.1,-15.4 -34.1,-34.1v-11.6L273.4,543.4c-19.1,0 -34.1,-15.4 -34.1,-34.1 0,-19.1 15,-34.1 34.1,-34.1h285.7v-11.9c0,-18.8 15.4,-34.1 34.1,-34.1s34.1,15.4 34.1,34.1v11.9h123.9c18.8,0 34.1,15 34.1,34.1 0,18.8 -15.4,34.1 -34.1,34.1zM751.3,323.9L405.2,323.9v6.8c0,18.8 -15.4,34.1 -34.1,34.1s-34.1,-15.4 -34.1,-34.1v-6.8L273.4,323.9c-19.1,0 -34.1,-15.4 -34.1,-34.1s15,-34.1 34.1,-34.1h63.5L336.9,238.9c0,-18.8 15.4,-34.1 34.1,-34.1s34.1,15.4 34.1,34.1v16.7h346.1c18.8,0 34.1,15.4 34.1,34.1s-15.4,34.1 -34.1,34.1z"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/tag.xml
Normal file
9
app/src/main/res/drawable/tag.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="200dp"
|
||||
android:height="200dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:pathData="M818.2,268.3c0.2,-34.9 -28.7,-64.3 -63.7,-64.6 -34.2,-0.3 -64.3,29.5 -64.3,64 0,34.3 28.9,63.6 63.3,64C788.2,332.1 818,302.9 818.2,268.3M128,640 L128,595.1c0,-0.4 1,-0.8 1,-1.1C132.3,570.2 144.4,551.5 161.2,534.8c125.6,-125.3 251,-250.8 376.5,-376.2C542.1,154.2 546.8,150 551.7,146.2 564,136.3 578.8,131.8 593.7,128l220.8,0c0.6,0.4 1.2,1 1.8,1.2 43.5,6.2 80.3,46.9 79.6,96.1 -0.8,63.2 -0.3,126.6 -0.1,189.9 0.1,26.6 -9.3,49.2 -28,67.9 -127.9,128 -255.7,256.1 -383.8,383.8 -39.2,39.1 -97.5,38.8 -136.8,-0.1 -63.6,-63.2 -126.7,-126.7 -190.1,-190 -11.9,-11.9 -20.6,-25.5 -25.3,-41.9C130.4,630.1 128,625.3 128,620.6"
|
||||
android:fillColor="#2c2c2c"/>
|
||||
</vector>
|
||||
Loading…
x
Reference in New Issue
Block a user