diff --git a/app/release/app-release.apk b/app/release/app-release.apk index 4c4b973..c479fbd 100644 Binary files a/app/release/app-release.apk and b/app/release/app-release.apk differ diff --git a/app/release/baselineProfiles/0/app-release.dm b/app/release/baselineProfiles/0/app-release.dm index 366611b..1399e30 100644 Binary files a/app/release/baselineProfiles/0/app-release.dm and b/app/release/baselineProfiles/0/app-release.dm differ diff --git a/app/release/baselineProfiles/1/app-release.dm b/app/release/baselineProfiles/1/app-release.dm index 9c739b5..0ebc934 100644 Binary files a/app/release/baselineProfiles/1/app-release.dm and b/app/release/baselineProfiles/1/app-release.dm differ diff --git a/app/src/main/java/com/jihe/punchnet/MainActivity.kt b/app/src/main/java/com/jihe/punchnet/MainActivity.kt index 8c1dc88..cce9d91 100644 --- a/app/src/main/java/com/jihe/punchnet/MainActivity.kt +++ b/app/src/main/java/com/jihe/punchnet/MainActivity.kt @@ -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 create(modelClass: Class): 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( diff --git a/app/src/main/java/com/jihe/punchnet/PunchnetService.kt b/app/src/main/java/com/jihe/punchnet/PunchnetService.kt index 6d0db66..830cfec 100644 --- a/app/src/main/java/com/jihe/punchnet/PunchnetService.kt +++ b/app/src/main/java/com/jihe/punchnet/PunchnetService.kt @@ -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(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) diff --git a/app/src/main/java/com/jihe/punchnet/data/ButtonViewModel.kt b/app/src/main/java/com/jihe/punchnet/data/ButtonViewModel.kt new file mode 100644 index 0000000..9b0f92b --- /dev/null +++ b/app/src/main/java/com/jihe/punchnet/data/ButtonViewModel.kt @@ -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 = MutableStateFlow(ButtonState.ButtonStopped) + val buttonState: StateFlow = _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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/jihe/punchnet/data/ServiceViewModel.kt b/app/src/main/java/com/jihe/punchnet/data/ServiceViewModel.kt index feaa3b3..4680284 100644 --- a/app/src/main/java/com/jihe/punchnet/data/ServiceViewModel.kt +++ b/app/src/main/java/com/jihe/punchnet/data/ServiceViewModel.kt @@ -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 = _isRunning + */ - fun startService(context: Context, routes: Array) { + fun startService(context: Context, routes: Array, 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) { + private fun startVpnService(context: Context, routes: Array, 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() } } \ No newline at end of file diff --git a/app/src/main/java/com/jihe/punchnet/helper/Screens.kt b/app/src/main/java/com/jihe/punchnet/helper/Screens.kt index cc9720a..c781d64 100644 --- a/app/src/main/java/com/jihe/punchnet/helper/Screens.kt +++ b/app/src/main/java/com/jihe/punchnet/helper/Screens.kt @@ -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") diff --git a/app/src/main/java/com/jihe/punchnet/screen/AppNavigationItems.kt b/app/src/main/java/com/jihe/punchnet/screen/AppNavigationItems.kt index b8111d8..54477dd 100644 --- a/app/src/main/java/com/jihe/punchnet/screen/AppNavigationItems.kt +++ b/app/src/main/java/com/jihe/punchnet/screen/AppNavigationItems.kt @@ -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() } } diff --git a/app/src/main/java/com/jihe/punchnet/screen/HomeScreen.kt b/app/src/main/java/com/jihe/punchnet/screen/HomeScreen.kt index 31ab650..ebe50d2 100644 --- a/app/src/main/java/com/jihe/punchnet/screen/HomeScreen.kt +++ b/app/src/main/java/com/jihe/punchnet/screen/HomeScreen.kt @@ -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, +) { + 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, 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 { "启动" } + */ ) } } diff --git a/app/src/main/java/com/jihe/punchnet/screen/MainScreen.kt b/app/src/main/java/com/jihe/punchnet/screen/MainScreen.kt index b28ae92..fccd210 100644 --- a/app/src/main/java/com/jihe/punchnet/screen/MainScreen.kt +++ b/app/src/main/java/com/jihe/punchnet/screen/MainScreen.kt @@ -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) } } diff --git a/app/src/main/java/com/jihe/punchnet/sdlan/network/libs.kt b/app/src/main/java/com/jihe/punchnet/sdlan/network/libs.kt index 3c104bc..4cb757c 100644 --- a/app/src/main/java/com/jihe/punchnet/sdlan/network/libs.kt +++ b/app/src/main/java/com/jihe/punchnet/sdlan/network/libs.kt @@ -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, 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(100) - // val start_stop_channel = Channel(100) + val start_stop_channel = Channel(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, - onConnected, - ::onMessage, - suspend { - node.aes.setSecret(null) - }, - null, - ) - } finally { - println("initTCPConn is cancelled") - } + initTCPConn( + scope, + tcp[0], tcp[1].toInt(), + start_stop_channel, + AtomicLong(now), + AtomicBoolean(false), + toSocket as ReceiveChannel, + onConnected, + ::onMessage, + suspend { + node.aes.setSecret(null) + }, + null, + ) } + println("sending start") start_stop_channel.send(StartStopChanInfo(StartStopFlag.IsStart, null)) + println("sent start") val cancel = Channel(100) 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) { val job_stun_request = scope.async { - while(true) { - delay(10_000) - node.sendStunRequest() + try { + while(true) { + delay(10_000) + node.sendStunRequest() + } + } catch (e: Exception) { } } val job_handle_packet = scope.async { - while(true) { - readAndParsePacket(scope, node, sock) + try { + while(true) { + readAndParsePacket(scope, node, sock) + } + } catch (e: Exception) { + sock.close() } } @@ -435,15 +447,17 @@ suspend fun initTCPConn( onDisconnected: suspend ()->Unit, connectingChan: SendChannel? ) { + 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,118 +481,130 @@ 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 } - val node = Node.getInstance() - val outIP = ByteBuffer.wrap(socket.localAddress.address).getInt() - node.outerIPV4.set(outIP) + try { + val node = Node.getInstance() + val outIP = ByteBuffer.wrap(socket.localAddress.address).getInt() + node.outerIPV4.set(outIP) - // val job_read_packet = CoroutineScope(Dispatchers.IO).async { - val job_read_packet = scope.async { - 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) { + // val job_read_packet = CoroutineScope(Dispatchers.IO).async { + val job_read_packet = scope.async { + val input = DataInputStream(socket.getInputStream()) try { - val v = start_stop.receive() - if (v.flag == StartStopFlag.IsStop) { - started.set(false) + 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 { + 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 } - } 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 { + 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 { - 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) } } diff --git a/app/src/main/java/com/jihe/punchnet/sdlan/network/socket.kt b/app/src/main/java/com/jihe/punchnet/sdlan/network/socket.kt index 913d708..78a4d1a 100644 --- a/app/src/main/java/com/jihe/punchnet/sdlan/network/socket.kt +++ b/app/src/main/java/com/jihe/punchnet/sdlan/network/socket.kt @@ -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 { val channel = Channel(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() } } diff --git a/app/src/main/res/drawable/preferences.xml b/app/src/main/res/drawable/preferences.xml new file mode 100644 index 0000000..3ba178a --- /dev/null +++ b/app/src/main/res/drawable/preferences.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/tag.xml b/app/src/main/res/drawable/tag.xml new file mode 100644 index 0000000..753c0cc --- /dev/null +++ b/app/src/main/res/drawable/tag.xml @@ -0,0 +1,9 @@ + + +