____ _ _______ __
/ __ \ | /| / / __/ |/ /
/ /_/ / |/ |/ / _// /
\____/|__/|__/___/_||_/
owenclave / app/src/main/java/io/nekohasekai/sagernet/ui/StunActivity.kt
/******************************************************************************
* Copyright (C) 2021 by nekohasekai *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
* *
******************************************************************************/
package io.nekohasekai.sagernet.ui
import android.os.Bundle
import android.view.MenuItem
import android.widget.ArrayAdapter
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.ListPopupWindow
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import io.nekohasekai.sagernet.R
import io.nekohasekai.sagernet.SagerNet
import io.nekohasekai.sagernet.database.DataStore
import io.nekohasekai.sagernet.databinding.LayoutStunBinding
import io.nekohasekai.sagernet.ktx.PUBLIC_STUN_SERVERS
import io.nekohasekai.sagernet.ktx.listByLineOrComma
import io.nekohasekai.sagernet.ktx.onMainDispatcher
import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher
import io.noties.markwon.Markwon
import libexclavecore.Libexclavecore
class StunActivity : ThemedActivity() {
private lateinit var binding: LayoutStunBinding
private val markwon by lazy { Markwon.create(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = LayoutStunBinding.inflate(layoutInflater)
setContentView(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
val ime = insets.getInsets(WindowInsetsCompat.Type.ime())
v.updatePadding(
bottom = ime.bottom,
)
insets
}
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.result_layout)) { v, insets ->
val bars = insets.getInsets(
WindowInsetsCompat.Type.systemBars()
or WindowInsetsCompat.Type.displayCutout()
)
val ime = insets.getInsets(WindowInsetsCompat.Type.ime())
v.updatePadding(
left = bars.left,
right = bars.right,
bottom = if (ime.bottom > bars.bottom) 0 else bars.bottom - ime.bottom,
)
insets
}
setSupportActionBar(findViewById(R.id.toolbar))
supportActionBar?.apply {
setTitle(R.string.stun_test)
setDisplayHomeAsUpEnabled(true)
setHomeAsUpIndicator(R.drawable.ic_navigation_close)
}
val list = if (DataStore.stunServers.isEmpty()) {
PUBLIC_STUN_SERVERS
} else {
DataStore.stunServers.listByLineOrComma().toTypedArray()
}
binding.natStunServer.setText(if (DataStore.stunServers.isEmpty()) list.random() else list[0])
binding.natStunServer.setOnClickListener {
val listPopupWindow = ListPopupWindow(this)
listPopupWindow.setAdapter(
ArrayAdapter(this, android.R.layout.simple_list_item_1, list)
)
listPopupWindow.setOnItemClickListener { _, _, i, _ ->
binding.natStunServer.setText(list[i])
listPopupWindow.dismiss()
}
listPopupWindow.anchorView = binding.natStunServer
listPopupWindow.show()
}
binding.stunRB.isChecked = true
binding.stunTest.setOnClickListener {
when {
binding.stunRB.isChecked -> doTest()
binding.stunLegacyRB.isChecked -> doLegacyTest()
}
}
binding.natMappingBehaviourCard.isVisible = false
binding.natFilteringBehaviourCard.isVisible = false
binding.natTypeCard.isVisible = false
binding.natExternalAddressCard.isVisible = false
}
fun doTest() {
binding.waitLayout.isVisible = true
binding.resultLayout.isVisible = false
runOnDefaultDispatcher {
val stunClient = Libexclavecore.newStunClient().apply {
if (SagerNet.started && DataStore.startedProfile > 0) {
useUDS(SagerNet.deviceStorage.noBackupFilesDir.toString() + "/ipc.sock")
useDNSUDS(SagerNet.deviceStorage.noBackupFilesDir.toString() + "/ipc_dns.sock")
}
}
val result = stunClient.stunTest(binding.natStunServer.text.toString())
onMainDispatcher {
if (result.error.isNotEmpty()) {
AlertDialog.Builder(this@StunActivity)
.setTitle(R.string.error_title)
.setMessage(result.error)
.setPositiveButton(android.R.string.ok) { _, _ -> }
.runCatching { show() }
}
binding.waitLayout.isVisible = false
binding.resultLayout.isVisible = true
markwon.setMarkdown(binding.natMappingBehaviour, result.natMapping)
markwon.setMarkdown(binding.natFilteringBehaviour, result.natFiltering)
binding.natMappingBehaviourCard.isVisible = true
binding.natFilteringBehaviourCard.isVisible = true
binding.natTypeCard.isVisible = false
binding.natExternalAddressCard.isVisible = false
}
}
}
fun doLegacyTest() {
binding.waitLayout.isVisible = true
binding.resultLayout.isVisible = false
runOnDefaultDispatcher {
val stunClient = Libexclavecore.newStunClient().apply {
if (SagerNet.started && DataStore.startedProfile > 0) {
useUDS(SagerNet.deviceStorage.noBackupFilesDir.toString() + "/ipc.sock")
useDNSUDS(SagerNet.deviceStorage.noBackupFilesDir.toString() + "/ipc_dns.sock")
}
}
val result = stunClient.stunLegacyTest(binding.natStunServer.text.toString())
onMainDispatcher {
if (result.error.isNotEmpty()) {
AlertDialog.Builder(this@StunActivity)
.setTitle(R.string.error_title)
.setMessage(result.error)
.setPositiveButton(android.R.string.ok) { _, _ -> }
.runCatching { show() }
}
binding.waitLayout.isVisible = false
binding.resultLayout.isVisible = true
markwon.setMarkdown(binding.natType, result.natType)
markwon.setMarkdown(binding.natExternalAddress, result.host)
binding.natMappingBehaviourCard.isVisible = false
binding.natFilteringBehaviourCard.isVisible = false
binding.natTypeCard.isVisible = true
binding.natExternalAddressCard.isVisible = true
}
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
finishAndRemoveTask()
true
}
else -> super.onOptionsItemSelected(item)
}
}
}