排查二次启动的问题
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
|
package com.jihe.punchnet
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
@ -31,12 +32,21 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.navigation.compose.rememberNavController
|
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.RouteViewModel
|
||||||
import com.jihe.punchnet.data.ServiceViewModel
|
import com.jihe.punchnet.data.ServiceViewModel
|
||||||
import com.jihe.punchnet.screen.MainApp
|
import com.jihe.punchnet.screen.MainApp
|
||||||
import com.jihe.punchnet.ui.theme.PunchnetTheme
|
import com.jihe.punchnet.ui.theme.PunchnetTheme
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
|
||||||
|
class PunchnetApp: Application() {
|
||||||
|
val buttonModel: ButtonViewModel by lazy {
|
||||||
|
ViewModelProvider.AndroidViewModelFactory.getInstance(this)
|
||||||
|
.create(ButtonViewModel::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
private val TAG = "MainActivity"
|
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() {
|
fun prepareAndStartVPN() {
|
||||||
@ -97,9 +119,12 @@ class MainActivity : ComponentActivity() {
|
|||||||
Log.d("DIR", "filesdir = ${this.filesDir}")
|
Log.d("DIR", "filesdir = ${this.filesDir}")
|
||||||
|
|
||||||
|
|
||||||
|
val buttonModel = ButtonViewModel()
|
||||||
|
buttonModel.updateState(ButtonState.ButtonStopped)
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
PunchnetTheme {
|
PunchnetTheme {
|
||||||
MainApp(serviceModel, viewModel, rememberNavController())
|
MainApp(buttonModel, serviceModel, viewModel, rememberNavController())
|
||||||
/*
|
/*
|
||||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||||
StartStop(
|
StartStop(
|
||||||
|
|||||||
@ -13,6 +13,9 @@ 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 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.RouteItem
|
||||||
import com.jihe.punchnet.data.RouteViewModel
|
import com.jihe.punchnet.data.RouteViewModel
|
||||||
import com.jihe.punchnet.sdlan.config.Arguments
|
import com.jihe.punchnet.sdlan.config.Arguments
|
||||||
@ -42,6 +45,7 @@ import java.io.FileOutputStream
|
|||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
class PunchnetService : VpnService() , IfaceTun {
|
class PunchnetService : VpnService() , IfaceTun {
|
||||||
|
|
||||||
private val TAG = "PunchnetService"
|
private val TAG = "PunchnetService"
|
||||||
private var scope = CoroutineScope(Dispatchers.IO)
|
private var scope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
@ -50,9 +54,7 @@ class PunchnetService : VpnService() , IfaceTun {
|
|||||||
private var vpnDescriptor: ParcelFileDescriptor? = null
|
private var vpnDescriptor: ParcelFileDescriptor? = null
|
||||||
|
|
||||||
private val serviceLock = Any()
|
private val serviceLock = Any()
|
||||||
private val isRunning = AtomicBoolean(false)
|
// private val isRunning = AtomicBoolean(false)
|
||||||
|
|
||||||
private val start_stop_channel = Channel<StartStopChanInfo>(100)
|
|
||||||
|
|
||||||
var input: FileInputStream? = null
|
var input: FileInputStream? = null
|
||||||
var output: FileOutputStream? = null
|
var output: FileOutputStream? = null
|
||||||
@ -129,17 +131,24 @@ class PunchnetService : VpnService() , IfaceTun {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vpnDescriptor = tempVpnDescriptor
|
vpnDescriptor = tempVpnDescriptor
|
||||||
.establish()
|
.establish()?.apply {
|
||||||
|
input = FileInputStream(fileDescriptor)
|
||||||
input = FileInputStream(vpnDescriptor!!.fileDescriptor)
|
output = FileOutputStream(fileDescriptor)
|
||||||
output = FileOutputStream(vpnDescriptor!!.fileDescriptor)
|
}
|
||||||
|
ButtonRepository.updateState(ButtonState.ButtonStarted)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun disconnect() {
|
private fun disconnect() {
|
||||||
synchronized(serviceLock) {
|
synchronized(serviceLock) {
|
||||||
|
if (ButtonRepository.buttonState.value != ButtonState.ButtonStarted) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
/*
|
||||||
if (!isRunning.get()) {
|
if (!isRunning.get()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
ButtonRepository.updateState(ButtonState.ButtonStopping)
|
||||||
|
|
||||||
Toast.makeText(this, "stop vpn called", Toast.LENGTH_LONG).show()
|
Toast.makeText(this, "stop vpn called", Toast.LENGTH_LONG).show()
|
||||||
input?.close()
|
input?.close()
|
||||||
@ -149,15 +158,23 @@ class PunchnetService : VpnService() , IfaceTun {
|
|||||||
|
|
||||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||||
stopSelf()
|
stopSelf()
|
||||||
isRunning.set(false)
|
// isRunning.set(false)
|
||||||
}
|
}
|
||||||
|
ButtonRepository.updateState(ButtonState.ButtonStopped)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun connect(startArg: PunchnetServiceArgument?) {
|
private fun connect(startArg: PunchnetServiceArgument?) {
|
||||||
synchronized(serviceLock) {
|
synchronized(serviceLock) {
|
||||||
|
if (ButtonRepository.buttonState.value != ButtonState.ButtonStopped) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
/*
|
||||||
if (isRunning.get()) {
|
if (isRunning.get()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
ButtonRepository.updateState(ButtonState.ButtonStarting)
|
||||||
|
|
||||||
val iface = this
|
val iface = this
|
||||||
val server = "punchnet.aioe.tech"
|
val server = "punchnet.aioe.tech"
|
||||||
@ -194,11 +211,13 @@ class PunchnetService : VpnService() , IfaceTun {
|
|||||||
startForeground(1, notification)
|
startForeground(1, notification)
|
||||||
|
|
||||||
scope.launch {
|
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 {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
@ -209,7 +228,6 @@ class PunchnetService : VpnService() , IfaceTun {
|
|||||||
disconnect()
|
disconnect()
|
||||||
START_STICKY
|
START_STICKY
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
scope = CoroutineScope(Dispatchers.IO)
|
scope = CoroutineScope(Dispatchers.IO)
|
||||||
arpTable = ARPTable(scope)
|
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.flow.toList
|
||||||
import kotlinx.coroutines.launch
|
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
|
||||||
|
*/
|
||||||
|
|
||||||
fun startService(context: Context, routes: Array<RouteItem>) {
|
fun startService(context: Context, routes: Array<RouteItem>, token: String) {
|
||||||
// should has the permission
|
// should has the permission
|
||||||
startVpnService(context, routes)
|
startVpnService(context, routes, token)
|
||||||
_isRunning.value = true
|
// 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)
|
val intent = Intent(context, PunchnetService::class.java)
|
||||||
intent.putExtra("argument", PunchnetServiceArgument(
|
intent.putExtra("argument", PunchnetServiceArgument(
|
||||||
"",
|
token,
|
||||||
routes,
|
routes,
|
||||||
))
|
))
|
||||||
|
|
||||||
@ -59,7 +63,7 @@ class ServiceViewModel(application: Application): AndroidViewModel(application)
|
|||||||
PunchnetService.ACTION_DISCONNECT })
|
PunchnetService.ACTION_DISCONNECT })
|
||||||
// stopService(Intent(this, PunchnetService::class.java))
|
// stopService(Intent(this, PunchnetService::class.java))
|
||||||
Log.d("STOPPED PUNCHNET", "stopping PUNCHNET")
|
Log.d("STOPPED PUNCHNET", "stopping PUNCHNET")
|
||||||
_isRunning.value = false
|
// ButtonRepository.updateState(ButtonState.ButtonStopping)
|
||||||
// Toast.makeText(this, "VPN service stopped", Toast.LENGTH_SHORT).show()
|
// Toast.makeText(this, "VPN service stopped", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,8 +1,48 @@
|
|||||||
package com.jihe.punchnet.helper
|
package com.jihe.punchnet.helper
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
import com.jihe.punchnet.data.RouteItem
|
import com.jihe.punchnet.data.RouteItem
|
||||||
import com.jihe.punchnet.sdlan.network.maskDigitToInt
|
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) {
|
sealed class Screen(val route: String) {
|
||||||
object ScreenRoutes: Screen("routes")
|
object ScreenRoutes: Screen("routes")
|
||||||
object ScreenMain: Screen("main")
|
object ScreenMain: Screen("main")
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import androidx.compose.material3.Icon
|
|||||||
import androidx.compose.material3.MaterialTheme
|
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.collectAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@ -34,29 +35,34 @@ import androidx.navigation.compose.composable
|
|||||||
import androidx.navigation.navArgument
|
import androidx.navigation.navArgument
|
||||||
import com.jihe.punchnet.R
|
import com.jihe.punchnet.R
|
||||||
import com.jihe.punchnet.RouteInfo
|
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.RouteViewModel
|
||||||
import com.jihe.punchnet.data.ServiceViewModel
|
import com.jihe.punchnet.data.ServiceViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AppNavHost2(
|
fun AppNavHost2(
|
||||||
|
buttonViewModel: ButtonViewModel,
|
||||||
serviceViewModel: ServiceViewModel,
|
serviceViewModel: ServiceViewModel,
|
||||||
routeViewModel: RouteViewModel = viewModel(),
|
routeViewModel: RouteViewModel = viewModel(),
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
paddingValues: PaddingValues
|
paddingValues: PaddingValues
|
||||||
) {
|
) {
|
||||||
|
|
||||||
NavHost(
|
NavHost(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = Screen.MainScreen.route,
|
startDestination = Screen.MainScreen.route,
|
||||||
modifier = Modifier.padding(paddingValues)
|
modifier = Modifier.padding(paddingValues)
|
||||||
) {
|
) {
|
||||||
composable(Screen.MainScreen.route) {
|
composable(Screen.MainScreen.route) {
|
||||||
HomeScreen(serviceViewModel, routeViewModel)
|
HomeScreen(buttonViewModel, serviceViewModel, routeViewModel)
|
||||||
}
|
}
|
||||||
composable(Screen.RouteScreen.route) {
|
composable(Screen.RouteScreen.route) {
|
||||||
CustomHeaderScreen(
|
CustomHeaderScreen(
|
||||||
"路由设置"
|
"路由设置"
|
||||||
) {
|
) {
|
||||||
RouteScreen(!serviceViewModel.isRunning.value, routeViewModel)
|
RouteScreen(ButtonRepository.buttonState.collectAsState().value != ButtonState.ButtonStarted, routeViewModel)
|
||||||
// LightScreen()
|
// LightScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package com.jihe.punchnet.screen
|
package com.jihe.punchnet.screen
|
||||||
|
|
||||||
|
import android.app.Activity.MODE_PRIVATE
|
||||||
import android.app.Activity.RESULT_CANCELED
|
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
|
||||||
@ -9,8 +10,15 @@ import android.os.Build
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.compose.foundation.Image
|
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.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
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.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Settings
|
import androidx.compose.material.icons.filled.Settings
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
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.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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@ -38,17 +55,116 @@ import com.jihe.punchnet.PunchnetService
|
|||||||
import com.jihe.punchnet.PunchnetServiceArgument
|
import com.jihe.punchnet.PunchnetServiceArgument
|
||||||
import com.jihe.punchnet.R
|
import com.jihe.punchnet.R
|
||||||
import com.jihe.punchnet.RouteInfo
|
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.RouteViewModel
|
||||||
import com.jihe.punchnet.data.ServiceViewModel
|
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
|
@Composable
|
||||||
fun HomeScreen(
|
fun HomeScreen(
|
||||||
|
buttonViewModel: ButtonViewModel,
|
||||||
serviceViewModel: ServiceViewModel,
|
serviceViewModel: ServiceViewModel,
|
||||||
routeViewModel: RouteViewModel,
|
routeViewModel: RouteViewModel,
|
||||||
// started: MutableState<Boolean>,
|
// started: MutableState<Boolean>,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val tkPref = getPreferenceString(context, PreferenceName.PreferenceToken)
|
||||||
|
|
||||||
|
var tokenvalue by remember {mutableStateOf(tkPref?:"")}
|
||||||
|
|
||||||
val allroutes = routeViewModel.allRoutes.collectAsState(emptyList())
|
val allroutes = routeViewModel.allRoutes.collectAsState(emptyList())
|
||||||
val vpnLauncher = rememberLauncherForActivityResult(
|
val vpnLauncher = rememberLauncherForActivityResult(
|
||||||
contract = ActivityResultContracts.StartActivityForResult()
|
contract = ActivityResultContracts.StartActivityForResult()
|
||||||
@ -57,7 +173,9 @@ fun HomeScreen(
|
|||||||
RESULT_OK -> {
|
RESULT_OK -> {
|
||||||
// serviceViewModel.startService(context)
|
// serviceViewModel.startService(context)
|
||||||
// serviceViewModel.setVPNPermission(true)
|
// 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 -> {
|
RESULT_CANCELED -> {
|
||||||
Toast.makeText(context, "vpn permission denied", Toast.LENGTH_SHORT).show()
|
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(
|
CustomHeaderScreen(
|
||||||
"",
|
"",
|
||||||
onBack = null,
|
onBack = null,
|
||||||
onMenu = {
|
onMenu = {
|
||||||
Icon(
|
HomeDropdownMenu(showTokenDialog)
|
||||||
Icons.Default.Settings,
|
|
||||||
contentDescription = "settings",
|
|
||||||
modifier = Modifier.fillMaxHeight()
|
|
||||||
)
|
|
||||||
// NavListMenu(navController, dbdao)
|
|
||||||
/*
|
|
||||||
navController.navigate(
|
|
||||||
Screen.ActionDetail.route.replace(
|
|
||||||
"{action_name}",
|
|
||||||
""
|
|
||||||
)
|
|
||||||
)
|
|
||||||
*/
|
|
||||||
|
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
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(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
.padding(top=50.dp),
|
.padding(top=50.dp),
|
||||||
@ -114,30 +259,38 @@ fun HomeScreen(
|
|||||||
|
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (serviceViewModel.isRunning.value) {
|
if (buttonViewModel.buttonState.value == ButtonState.ButtonStarted) {
|
||||||
|
// if (serviceViewModel.isRunning.value) {
|
||||||
// if is running, should stop service
|
// if is running, should stop service
|
||||||
serviceViewModel.stopVpnService(context)
|
serviceViewModel.stopVpnService(context)
|
||||||
} else {
|
} else if (buttonViewModel.buttonState.value == ButtonState.ButtonStopped){
|
||||||
val intent = VpnService.prepare(context)
|
val intent = VpnService.prepare(context)
|
||||||
if (intent != null) {
|
if (intent != null) {
|
||||||
vpnLauncher.launch(intent)
|
vpnLauncher.launch(intent)
|
||||||
} else {
|
} 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
|
// started.value = !started.value
|
||||||
},
|
},
|
||||||
|
enabled = buttonState.value.enabled,
|
||||||
shape = RoundedCornerShape(10.dp),
|
shape = RoundedCornerShape(10.dp),
|
||||||
modifier = Modifier.padding(top=48.dp)
|
modifier = Modifier.padding(top=48.dp)
|
||||||
.width(120.dp)
|
.width(120.dp)
|
||||||
.height(40.dp)
|
.height(40.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
|
buttonState.value.text
|
||||||
|
// buttonViewModel.buttonText.value
|
||||||
|
/*
|
||||||
if (serviceViewModel.isRunning.value) {
|
if (serviceViewModel.isRunning.value) {
|
||||||
"停止"
|
"停止"
|
||||||
} else {
|
} else {
|
||||||
"启动"
|
"启动"
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
|
import com.jihe.punchnet.data.ButtonViewModel
|
||||||
import com.jihe.punchnet.data.RouteViewModel
|
import com.jihe.punchnet.data.RouteViewModel
|
||||||
import com.jihe.punchnet.data.ServiceViewModel
|
import com.jihe.punchnet.data.ServiceViewModel
|
||||||
|
|
||||||
@ -37,6 +38,7 @@ val bottomNavItems = listOf(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MainApp(
|
fun MainApp(
|
||||||
|
buttonViewModel: ButtonViewModel = viewModel(),
|
||||||
serviceViewModel: ServiceViewModel = viewModel(),
|
serviceViewModel: ServiceViewModel = viewModel(),
|
||||||
routeModel: RouteViewModel = viewModel(),
|
routeModel: RouteViewModel = viewModel(),
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
@ -54,7 +56,7 @@ fun MainApp(
|
|||||||
modifier = Modifier.padding(paddingValues).size(200.dp)
|
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 android.util.Log
|
||||||
import com.jihe.punchnet.PunchnetServiceArgument
|
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.SDLDevAddr
|
||||||
import com.jihe.punchnet.protobuf.PunchProto.SDLRegisterSuper
|
import com.jihe.punchnet.protobuf.PunchProto.SDLRegisterSuper
|
||||||
import com.jihe.punchnet.protobuf.PunchProto.SDLRegisterSuperAck
|
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.ipToString
|
||||||
import com.jihe.punchnet.sdlan.utils.macToString
|
import com.jihe.punchnet.sdlan.utils.macToString
|
||||||
import com.jihe.punchnet.sdlan.utils.parseScoketAddressV4FromString
|
import com.jihe.punchnet.sdlan.utils.parseScoketAddressV4FromString
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.NonCancellable
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.cancelAndJoin
|
import kotlinx.coroutines.cancelAndJoin
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.channels.ReceiveChannel
|
import kotlinx.coroutines.channels.ReceiveChannel
|
||||||
@ -91,9 +96,11 @@ suspend fun onMessage(scope: CoroutineScope, data: SDLanTCP) {
|
|||||||
when(nakcode) {
|
when(nakcode) {
|
||||||
NakMsgCode.InvalidToken, NakMsgCode.NodeDisabled-> {
|
NakMsgCode.InvalidToken, NakMsgCode.NodeDisabled-> {
|
||||||
node.startStopChannel.send(StartStopChanInfo(StartStopFlag.IsStop, null))
|
node.startStopChannel.send(StartStopChanInfo(StartStopFlag.IsStop, null))
|
||||||
exitProcess(-1)
|
ButtonRepository.updateState(ButtonState.ButtonStopped)
|
||||||
|
scope.cancel()
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
|
ButtonRepository.updateState(ButtonState.ButtonStopped)
|
||||||
node.startStopChannel.send(StartStopChanInfo(StartStopFlag.IsStop, null))
|
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()}")
|
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)
|
UniqueNodeID.setBaseDir(argument.baseDir)
|
||||||
val edgeUUID = UniqueNodeID.getUUID()
|
val edgeUUID = UniqueNodeID.getUUID()
|
||||||
val config = parseConfig(edgeUUID, argument)
|
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 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)
|
initEdge(scope, iface, argument.token, config, toSocket, start_stop_channel)
|
||||||
|
|
||||||
val tcp = argument.tcp.split(":")
|
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 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 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)
|
val encrypted = node.rsa.encrypt(before.toByteArray(), use_private_key = false)
|
||||||
// println("encrypted: ${encrypted.contentToString()}")
|
// println("encrypted: ${encrypted.contentToString()}")
|
||||||
@ -227,6 +232,8 @@ suspend fun run_sdlan(scope: CoroutineScope, start_stop_channel: Channel<StartSt
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
println("iface read is cancelled")
|
println("iface read is cancelled")
|
||||||
node.iface?.close()
|
node.iface?.close()
|
||||||
|
node.udpSockV4.close()
|
||||||
|
node.udpSockMulticast?.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
@ -253,28 +260,26 @@ suspend fun run_sdlan(scope: CoroutineScope, start_stop_channel: Channel<StartSt
|
|||||||
|
|
||||||
// CoroutineScope(Dispatchers.IO).async {
|
// CoroutineScope(Dispatchers.IO).async {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
initTCPConn(
|
||||||
initTCPConn(
|
scope,
|
||||||
scope,
|
tcp[0], tcp[1].toInt(),
|
||||||
tcp[0], tcp[1].toInt(),
|
start_stop_channel,
|
||||||
start_stop_channel,
|
AtomicLong(now),
|
||||||
AtomicLong(now),
|
AtomicBoolean(false),
|
||||||
AtomicBoolean(false),
|
toSocket as ReceiveChannel<ByteArray>,
|
||||||
toSocket as ReceiveChannel<ByteArray>,
|
onConnected,
|
||||||
onConnected,
|
::onMessage,
|
||||||
::onMessage,
|
suspend {
|
||||||
suspend {
|
node.aes.setSecret(null)
|
||||||
node.aes.setSecret(null)
|
},
|
||||||
},
|
null,
|
||||||
null,
|
)
|
||||||
)
|
|
||||||
} finally {
|
|
||||||
println("initTCPConn is cancelled")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
println("sending start")
|
||||||
start_stop_channel.send(StartStopChanInfo(StartStopFlag.IsStart, null))
|
start_stop_channel.send(StartStopChanInfo(StartStopFlag.IsStart, null))
|
||||||
|
println("sent start")
|
||||||
|
|
||||||
val cancel = Channel<Boolean>(100)
|
val cancel = Channel<Boolean>(100)
|
||||||
runEdgeLoop(scope, node, cancel)
|
runEdgeLoop(scope, node, cancel)
|
||||||
@ -295,15 +300,22 @@ suspend fun runEdgeLoop(scope: CoroutineScope, node: Node, cancel: ReceiveChanne
|
|||||||
|
|
||||||
suspend fun loopSocketV4(scope: CoroutineScope, node: Node, sock: SDLanSocket, cancel: ReceiveChannel<Boolean>) {
|
suspend fun loopSocketV4(scope: CoroutineScope, node: Node, sock: SDLanSocket, cancel: ReceiveChannel<Boolean>) {
|
||||||
val job_stun_request = scope.async {
|
val job_stun_request = scope.async {
|
||||||
while(true) {
|
try {
|
||||||
delay(10_000)
|
while(true) {
|
||||||
node.sendStunRequest()
|
delay(10_000)
|
||||||
|
node.sendStunRequest()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val job_handle_packet = scope.async {
|
val job_handle_packet = scope.async {
|
||||||
while(true) {
|
try {
|
||||||
readAndParsePacket(scope, node, sock)
|
while(true) {
|
||||||
|
readAndParsePacket(scope, node, sock)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
sock.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -435,15 +447,17 @@ suspend fun initTCPConn(
|
|||||||
onDisconnected: suspend ()->Unit,
|
onDisconnected: suspend ()->Unit,
|
||||||
connectingChan: SendChannel<ConnectingState>?
|
connectingChan: SendChannel<ConnectingState>?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val started = AtomicBoolean(false)
|
val started = AtomicBoolean(false)
|
||||||
// var started: Boolean = false
|
// var started: Boolean = false
|
||||||
var startPktID: Int? = null
|
var startPktID: Int? = null
|
||||||
|
val node = Node.getInstance()
|
||||||
|
|
||||||
while(true) {
|
while (true) {
|
||||||
connectingChan?.send(ConnectingState.NotConnected)
|
connectingChan?.send(ConnectingState.NotConnected)
|
||||||
|
|
||||||
if (!started.get()) {
|
if (!started.get()) {
|
||||||
while(true) {
|
while (true) {
|
||||||
println("waiting for start_stop")
|
println("waiting for start_stop")
|
||||||
val startStopInfo = start_stop.receive()
|
val startStopInfo = start_stop.receive()
|
||||||
if (startStopInfo.flag == StartStopFlag.IsStart) {
|
if (startStopInfo.flag == StartStopFlag.IsStart) {
|
||||||
@ -459,7 +473,7 @@ suspend fun initTCPConn(
|
|||||||
}
|
}
|
||||||
|
|
||||||
connectingChan?.send(ConnectingState.Connecting)
|
connectingChan?.send(ConnectingState.Connecting)
|
||||||
TerminalLogger.debugf {"try connecting tcp..."}
|
TerminalLogger.debugf { "try connecting tcp..." }
|
||||||
|
|
||||||
val socket: Socket
|
val socket: Socket
|
||||||
try {
|
try {
|
||||||
@ -467,118 +481,130 @@ suspend fun initTCPConn(
|
|||||||
TerminalLogger.debugf { "connecting to $tcpHost:$tcpPort" }
|
TerminalLogger.debugf { "connecting to $tcpHost:$tcpPort" }
|
||||||
socket = Socket(tcpHost, tcpPort)
|
socket = Socket(tcpHost, tcpPort)
|
||||||
}
|
}
|
||||||
|
}catch(e: CancellationException) {
|
||||||
|
withContext(NonCancellable) {
|
||||||
|
node.iface?.close()
|
||||||
|
}
|
||||||
|
return
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
TerminalLogger.errorf { "failed to connect to ${tcpHost}:${tcpPort}: $e"}
|
TerminalLogger.errorf { "failed to connect to ${tcpHost}:${tcpPort}: $e" }
|
||||||
delay(3000)
|
delay(3000)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
val node = Node.getInstance()
|
try {
|
||||||
val outIP = ByteBuffer.wrap(socket.localAddress.address).getInt()
|
val node = Node.getInstance()
|
||||||
node.outerIPV4.set(outIP)
|
val outIP = ByteBuffer.wrap(socket.localAddress.address).getInt()
|
||||||
|
node.outerIPV4.set(outIP)
|
||||||
|
|
||||||
// val job_read_packet = CoroutineScope(Dispatchers.IO).async {
|
// val job_read_packet = CoroutineScope(Dispatchers.IO).async {
|
||||||
val job_read_packet = scope.async {
|
val job_read_packet = scope.async {
|
||||||
val input = DataInputStream(socket.getInputStream())
|
val input = DataInputStream(socket.getInputStream())
|
||||||
try {
|
|
||||||
println("job read packet starts")
|
|
||||||
while(true) {
|
|
||||||
val tcpPacket = readPacket(input)
|
|
||||||
if (tcpPacket == null) {
|
|
||||||
TerminalLogger.errorf {"tcp Packet is null"}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
onMessage(scope, tcpPacket)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
TerminalLogger.errorf {"input closing"}
|
|
||||||
input.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// val job_write_to_packet = CoroutineScope(Dispatchers.IO).async {
|
|
||||||
val job_write_to_packet = scope.async {
|
|
||||||
val output = DataOutputStream(socket.getOutputStream())
|
|
||||||
try {
|
|
||||||
TerminalLogger.debugf {"job write to packet starts"}
|
|
||||||
while(true) {
|
|
||||||
try {
|
|
||||||
val msg = toSocket.receive()
|
|
||||||
TerminalLogger.debugf{"received message"}
|
|
||||||
output.write(msg)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
output.close()
|
|
||||||
TerminalLogger.errorf {"failed to receive message to tcp: $e"}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
output.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// val job_check_pong = CoroutineScope(Dispatchers.IO).async {
|
|
||||||
val job_check_pong = scope.async {
|
|
||||||
println("job check pong starts")
|
|
||||||
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"}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// val job_check_stop = CoroutineScope(Dispatchers.IO).async {
|
|
||||||
val job_check_stop = scope.async {
|
|
||||||
println("job check stop starts")
|
|
||||||
while(true) {
|
|
||||||
try {
|
try {
|
||||||
val v = start_stop.receive()
|
println("job read packet starts")
|
||||||
if (v.flag == StartStopFlag.IsStop) {
|
while (true) {
|
||||||
started.set(false)
|
val tcpPacket = readPacket(input)
|
||||||
|
if (tcpPacket == null) {
|
||||||
|
TerminalLogger.errorf { "tcp Packet is null" }
|
||||||
|
break
|
||||||
|
}
|
||||||
|
onMessage(scope, tcpPacket)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
input.close()
|
||||||
|
TerminalLogger.errorf { "input closing" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// val job_write_to_packet = CoroutineScope(Dispatchers.IO).async {
|
||||||
|
val job_write_to_packet = scope.async {
|
||||||
|
val output = DataOutputStream(socket.getOutputStream())
|
||||||
|
try {
|
||||||
|
TerminalLogger.debugf { "job write to packet starts" }
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
val msg = toSocket.receive()
|
||||||
|
TerminalLogger.debugf { "received message" }
|
||||||
|
output.write(msg)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
output.close()
|
||||||
|
TerminalLogger.errorf { "failed to receive message to tcp: $e" }
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
output.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// val job_check_pong = CoroutineScope(Dispatchers.IO).async {
|
||||||
|
val job_check_pong = scope.async {
|
||||||
|
println("job check pong starts")
|
||||||
|
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" }
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} catch(e: Exception) {
|
|
||||||
started.set(false)
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// val job_check_stop = CoroutineScope(Dispatchers.IO).async {
|
||||||
|
val job_check_stop = scope.async {
|
||||||
|
println("job check stop starts")
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
val v = start_stop.receive()
|
||||||
|
if (v.flag == StartStopFlag.IsStop) {
|
||||||
|
started.set(false)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// started.set(false)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TerminalLogger.debugf { "connected" }
|
||||||
|
onConnected(socket, startPktID)
|
||||||
|
connectingChan?.send(ConnectingState.Connected)
|
||||||
|
|
||||||
|
var cancelled: Boolean = false
|
||||||
|
select<Unit> {
|
||||||
|
job_read_packet.onAwait() {
|
||||||
|
println("job read packet exited")
|
||||||
|
}
|
||||||
|
job_write_to_packet.onAwait() {
|
||||||
|
println("job write to packet exited")
|
||||||
|
}
|
||||||
|
job_check_pong.onAwait() {
|
||||||
|
println("job check pong exited")
|
||||||
|
}
|
||||||
|
job_check_stop.onAwait() {
|
||||||
|
println("job check stop exited")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println("m1")
|
||||||
|
job_read_packet.cancelAndJoin()
|
||||||
|
println("m2")
|
||||||
|
job_write_to_packet.cancelAndJoin()
|
||||||
|
println("m3")
|
||||||
|
job_check_pong.cancelAndJoin()
|
||||||
|
println("m4")
|
||||||
|
job_check_stop.cancelAndJoin()
|
||||||
|
println("m5")
|
||||||
|
|
||||||
|
delay(1000)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
withContext(NonCancellable) {
|
||||||
|
node.iface?.close()
|
||||||
|
socket.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TerminalLogger.debugf { "connected" }
|
|
||||||
onConnected(socket, startPktID)
|
|
||||||
connectingChan?.send(ConnectingState.Connected)
|
|
||||||
|
|
||||||
var cancelled: Boolean = false
|
|
||||||
select<Unit> {
|
|
||||||
job_read_packet.onAwait() {
|
|
||||||
println("job read packet exited")
|
|
||||||
}
|
|
||||||
job_write_to_packet.onAwait() {
|
|
||||||
println("job write to packet exited")
|
|
||||||
}
|
|
||||||
job_check_pong.onAwait() {
|
|
||||||
println("job check pong exited")
|
|
||||||
}
|
|
||||||
job_check_stop.onAwait() {
|
|
||||||
println("job check stop exited")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
println("m1")
|
|
||||||
job_read_packet.cancelAndJoin()
|
|
||||||
println("m2")
|
|
||||||
job_write_to_packet.cancelAndJoin()
|
|
||||||
println("m3")
|
|
||||||
job_check_pong.cancelAndJoin()
|
|
||||||
println("m4")
|
|
||||||
job_check_stop.cancelAndJoin()
|
|
||||||
println("m5")
|
|
||||||
|
|
||||||
delay(1000)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -23,8 +23,6 @@ class SDLanSocket(val scope: CoroutineScope, val addr: String, val port: Int, va
|
|||||||
sock
|
sock
|
||||||
}
|
}
|
||||||
|
|
||||||
private var job: Job? = null
|
|
||||||
|
|
||||||
fun loop(): ReceiveChannel<DatagramPacket> {
|
fun loop(): ReceiveChannel<DatagramPacket> {
|
||||||
val channel = Channel<DatagramPacket>(100)
|
val channel = Channel<DatagramPacket>(100)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@ -44,7 +42,7 @@ class SDLanSocket(val scope: CoroutineScope, val addr: String, val port: Int, va
|
|||||||
connection.send(packet)
|
connection.send(packet)
|
||||||
}
|
}
|
||||||
} catch(e: Exception) {
|
} 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() {
|
suspend fun close() {
|
||||||
job?.cancelAndJoin()
|
|
||||||
connection.close()
|
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