Add Enable Air option support

Use proper method to pass client address to server.
Fix possible memory leak.
pull/1/head
Tindy X 2021-03-13 01:01:44 +08:00
parent 815cf3cadf
commit 45bc2fa054
No known key found for this signature in database
GPG Key ID: C6AD413169968D58
4 changed files with 80 additions and 53 deletions

View File

@ -13,6 +13,16 @@ class BrokenithmApplication : Application() {
config.edit().putString("server", value).apply() config.edit().putString("server", value).apply()
} }
var enableAir: Boolean
get() {
val config = getSharedPreferences(settings_preference, MODE_PRIVATE)
return config.getBoolean("enable_air", true)
}
set(value) {
val config = getSharedPreferences(settings_preference, MODE_PRIVATE)
config.edit().putBoolean("enable_air", value).apply()
}
var simpleAir: Boolean var simpleAir: Boolean
get() { get() {
val config = getSharedPreferences(settings_preference, MODE_PRIVATE) val config = getSharedPreferences(settings_preference, MODE_PRIVATE)

View File

@ -46,7 +46,7 @@ class MainActivity : AppCompatActivity() {
private var mTestButton = false private var mTestButton = false
private var mServiceButton = false private var mServiceButton = false
private data class InputEvent(val keys: MutableSet<Int>? = null, val airHeight : Int = 6, val testButton: Boolean = false, val serviceButton: Boolean = false) private data class InputEvent(val keys: MutableSet<Int>? = null, val airHeight : Int = 6, val testButton: Boolean = false, val serviceButton: Boolean = false)
private var mInputQueue = ArrayDeque<InputEvent>() //private var mInputQueue = ArrayDeque<InputEvent>()
// LEDs // LEDs
private lateinit var mLEDBitmap: Bitmap private lateinit var mLEDBitmap: Bitmap
@ -241,26 +241,38 @@ class MainActivity : AppCompatActivity() {
findViewById<Button>(R.id.button_coin).setOnClickListener { findViewById<Button>(R.id.button_coin).setOnClickListener {
if(!mExitFlag) if(!mExitFlag)
sendFunctionKey(parseAddress(editServer.text.toString()), FUNCTION_BUTTON.FUNCTION_COIN) sendFunctionKey(parseAddress(editServer.text.toString()), FunctionButton.FUNCTION_COIN)
} }
findViewById<Button>(R.id.button_card).setOnClickListener { findViewById<Button>(R.id.button_card).setOnClickListener {
if(!mExitFlag) if(!mExitFlag)
sendFunctionKey(parseAddress(editServer.text.toString()), FUNCTION_BUTTON.FUNCTION_CARD) sendFunctionKey(parseAddress(editServer.text.toString()), FunctionButton.FUNCTION_CARD)
} }
findViewById<CheckBox>(R.id.check_simple_air).apply {
val checkSimpleAir = findViewById<CheckBox>(R.id.check_simple_air)
findViewById<CheckBox>(R.id.check_enable_air).apply {
setOnCheckedChangeListener { _, isChecked ->
mEnableAir = isChecked
checkSimpleAir.isEnabled = isChecked
app.enableAir = isChecked
}
isChecked = app.enableAir
}
checkSimpleAir.apply {
setOnCheckedChangeListener { _, isChecked -> setOnCheckedChangeListener { _, isChecked ->
mSimpleAir = isChecked mSimpleAir = isChecked
app.simpleAir = isChecked app.simpleAir = isChecked
} }
isChecked = app.simpleAir isChecked = app.simpleAir
} }
mEnableAir = app.enableAir
mSimpleAir = app.simpleAir
findViewById<View>(R.id.button_test).setOnTouchListener { view, event -> findViewById<View>(R.id.button_test).setOnTouchListener { view, event ->
mTestButton = when(event.actionMasked) { mTestButton = when(event.actionMasked) {
MotionEvent.ACTION_MOVE, MotionEvent.ACTION_DOWN -> true MotionEvent.ACTION_MOVE, MotionEvent.ACTION_DOWN -> true
else -> false else -> false
} }
mInputQueue.add(InputEvent(serviceButton = mServiceButton, testButton = mTestButton)) //mInputQueue.add(InputEvent(serviceButton = mServiceButton, testButton = mTestButton))
view.performClick() view.performClick()
} }
findViewById<View>(R.id.button_service).setOnTouchListener { view, event -> findViewById<View>(R.id.button_service).setOnTouchListener { view, event ->
@ -268,7 +280,7 @@ class MainActivity : AppCompatActivity() {
MotionEvent.ACTION_MOVE, MotionEvent.ACTION_DOWN -> true MotionEvent.ACTION_MOVE, MotionEvent.ACTION_DOWN -> true
else -> false else -> false
} }
mInputQueue.add(InputEvent(serviceButton = mServiceButton, testButton = mTestButton)) //mInputQueue.add(InputEvent(serviceButton = mServiceButton, testButton = mTestButton))
view.performClick() view.performClick()
} }
@ -382,7 +394,7 @@ class MainActivity : AppCompatActivity() {
} }
} else { } else {
val socket = try { val socket = try {
DatagramSocket(52468).apply { DatagramSocket(serverPort).apply {
reuseAddress = true reuseAddress = true
soTimeout = 1000 soTimeout = 1000
} }
@ -497,7 +509,7 @@ class MainActivity : AppCompatActivity() {
) )
} }
enum class FUNCTION_BUTTON { enum class FunctionButton {
UNDEFINED, FUNCTION_COIN, FUNCTION_CARD UNDEFINED, FUNCTION_COIN, FUNCTION_CARD
} }
@ -510,33 +522,20 @@ class MainActivity : AppCompatActivity() {
var serviceBtn = false var serviceBtn = false
} }
private fun sendAir(server: String, port: Int) { private fun getLocalIPAddress(useIPv4: Boolean): ByteArray {
val address = InetSocketAddress(server, port)
val socket = DatagramSocket()
val buffer = byteArrayOf(0x04, 'A'.toByte(), 'I'.toByte(), 'R'.toByte(), if (mEnableAir) 0x01 else 0x00)
val packet = DatagramPacket(buffer, buffer.size)
socket.apply {
connect(address)
send(packet)
close()
}
}
private fun getLocalIPAddress(useIPv4: Boolean): String {
try { try {
val interfaces: List<NetworkInterface> = Collections.list(NetworkInterface.getNetworkInterfaces()) val interfaces: List<NetworkInterface> = Collections.list(NetworkInterface.getNetworkInterfaces())
for (intf in interfaces) { for (intf in interfaces) {
val addrs: List<InetAddress> = Collections.list(intf.inetAddresses) val addrs: List<InetAddress> = Collections.list(intf.inetAddresses)
for (addr in addrs) { for (addr in addrs) {
if (!addr.isLoopbackAddress) { if (!addr.isLoopbackAddress) {
val sAddr = addr.hostAddress val sAddr = addr.address
val isIPv4 = sAddr.indexOf(':') < 0 val isIPv4 = sAddr.size == 4
if (useIPv4) { if (useIPv4) {
if (isIPv4) return sAddr if (isIPv4) return sAddr
} else { } else {
if (!isIPv4) { if (!isIPv4) {
val delim = sAddr.indexOf('%') // drop ip6 zone suffix return sAddr
return if (delim < 0) sAddr.toUpperCase(Locale.ROOT) else sAddr.substring(0, delim).toUpperCase(Locale.ROOT)
} }
} }
} }
@ -544,17 +543,21 @@ class MainActivity : AppCompatActivity() {
} }
} catch (e: Exception) { } catch (e: Exception) {
} }
return "" return byteArrayOf()
} }
private fun sendConnect(address: InetSocketAddress?) { private fun sendConnect(address: InetSocketAddress?) {
address ?: return address ?: return
thread { thread {
val selfAddress = getLocalIPAddress(true) val selfAddress = getLocalIPAddress(true)
val buffer = ByteArray(24) if (selfAddress.isEmpty()) return@thread
byteArrayOf(23, 'C'.toByte(), 'O'.toByte(), 'N'.toByte()).copyInto(buffer) val buffer = ByteArray(21)
selfAddress.toByteArray().copyInto(buffer, 4) byteArrayOf('C'.toByte(), 'O'.toByte(), 'N'.toByte()).copyInto(buffer, 1)
serverPort.toString().toByteArray().copyInto(buffer, 4 + 15) ByteBuffer.wrap(buffer)
.put(4, if (selfAddress.size == 4) 1.toByte() else 2.toByte())
.putShort(5, serverPort.toShort())
selfAddress.copyInto(buffer, 7)
buffer[0] = (3 + 1 + 2 + selfAddress.size).toByte()
try { try {
val socket = DatagramSocket() val socket = DatagramSocket()
val packet = DatagramPacket(buffer, buffer.size) val packet = DatagramPacket(buffer, buffer.size)
@ -596,7 +599,7 @@ class MainActivity : AppCompatActivity() {
} }
} }
private fun sendFunctionKey(address: InetSocketAddress?, function: FUNCTION_BUTTON) { private fun sendFunctionKey(address: InetSocketAddress?, function: FunctionButton) {
address ?: return address ?: return
thread { thread {
val buffer = byteArrayOf(4, 'F'.toByte(), 'N'.toByte(), 'C'.toByte(), function.ordinal.toByte()) val buffer = byteArrayOf(4, 'F'.toByte(), 'N'.toByte(), 'C'.toByte(), function.ordinal.toByte())
@ -667,7 +670,8 @@ class MainActivity : AppCompatActivity() {
val realBuf = ByteArray(44) val realBuf = ByteArray(44)
realBuf[0] = buffer.length.toByte() realBuf[0] = buffer.length.toByte()
buffer.header.copyInto(realBuf, 1) buffer.header.copyInto(realBuf, 1)
buffer.air.copyInto(realBuf, 4) if (mEnableAir)
buffer.air.copyInto(realBuf, 4)
buffer.slider.copyInto(realBuf, 10) buffer.slider.copyInto(realBuf, 10)
realBuf[42] = if (buffer.testBtn) 0x01 else 0x00 realBuf[42] = if (buffer.testBtn) 0x01 else 0x00
realBuf[43] = if (buffer.serviceBtn) 0x01 else 0x00 realBuf[43] = if (buffer.serviceBtn) 0x01 else 0x00
@ -684,7 +688,6 @@ class MainActivity : AppCompatActivity() {
private var mLastAirUpdateTime = 0L private var mLastAirUpdateTime = 0L
private fun applyKeys(event: InputEvent, buffer: IoBuffer): IoBuffer { private fun applyKeys(event: InputEvent, buffer: IoBuffer): IoBuffer {
return buffer.apply { return buffer.apply {
buffer.length = 43
buffer.header = byteArrayOf('I'.toByte(), 'N'.toByte(), 'P'.toByte()) buffer.header = byteArrayOf('I'.toByte(), 'N'.toByte(), 'P'.toByte())
if (event.keys != null && event.keys.isNotEmpty()) { if (event.keys != null && event.keys.isNotEmpty()) {
for (i in 0 until 32) { for (i in 0 until 32) {
@ -692,16 +695,20 @@ class MainActivity : AppCompatActivity() {
} }
} }
val currentTime = System.currentTimeMillis() buffer.length = if (mEnableAir) {
if (currentTime - mLastAirUpdateTime > airUpdateInterval) { val currentTime = System.currentTimeMillis()
mLastAirHeight += if (mLastAirHeight < mCurrentAirHeight) 1 else if (mLastAirHeight > mCurrentAirHeight) -1 else 0 if (currentTime - mLastAirUpdateTime > airUpdateInterval) {
mLastAirUpdateTime = currentTime mLastAirHeight += if (mLastAirHeight < mCurrentAirHeight) 1 else if (mLastAirHeight > mCurrentAirHeight) -1 else 0
} mLastAirUpdateTime = currentTime
if (mLastAirHeight != 6) {
for (i in mLastAirHeight..5) {
buffer.air[mAirIdx[i]] = 1
} }
} if (mLastAirHeight != 6) {
for (i in mLastAirHeight..5) {
buffer.air[mAirIdx[i]] = 1
}
}
43
} else 37
buffer.serviceBtn = event.serviceButton buffer.serviceBtn = event.serviceButton
buffer.testBtn = event.testButton buffer.testBtn = event.testButton
} }

View File

@ -111,12 +111,12 @@
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<CheckBox <CheckBox
android:id="@+id/check_simple_air" android:id="@+id/check_enable_air"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:text="@string/simple_air" android:text="Enable Air"
android:textColor="@color/white" android:textColor="@color/white"
app:layout_constraintStart_toEndOf="@+id/button_card" app:layout_constraintStart_toEndOf="@+id/button_card"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@ -168,10 +168,10 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:background="@drawable/highlight_border_1dp" android:background="@drawable/highlight_border_1dp"
android:text="@string/test"
android:textColor="@color/white"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:text="@string/test"
android:textColor="@color/white"
app:layout_constraintStart_toEndOf="@id/check_debug" app:layout_constraintStart_toEndOf="@id/check_debug"
app:layout_constraintTop_toTopOf="@+id/check_debug" /> app:layout_constraintTop_toTopOf="@+id/check_debug" />
@ -192,7 +192,7 @@
android:id="@+id/check_show_delay" android:id="@+id/check_show_delay"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="4dp"
android:text="@string/show_delay" android:text="@string/show_delay"
android:textColor="@color/white" android:textColor="@color/white"
app:layout_constraintStart_toEndOf="@+id/button_service" app:layout_constraintStart_toEndOf="@+id/button_service"
@ -202,12 +202,22 @@
android:id="@+id/check_vibrate" android:id="@+id/check_vibrate"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="4dp"
android:text="@string/enable_vibration" android:text="@string/enable_vibration"
android:textColor="@color/white" android:textColor="@color/white"
app:layout_constraintStart_toEndOf="@+id/check_show_delay" app:layout_constraintStart_toEndOf="@+id/check_show_delay"
app:layout_constraintTop_toTopOf="@+id/check_show_delay" /> app:layout_constraintTop_toTopOf="@+id/check_show_delay" />
<CheckBox
android:id="@+id/check_simple_air"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:text="@string/simple_air"
android:textColor="@color/white"
app:layout_constraintStart_toEndOf="@+id/check_vibrate"
app:layout_constraintTop_toTopOf="@+id/check_vibrate" />
<TextView <TextView
android:id="@+id/text_mode" android:id="@+id/text_mode"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -222,9 +232,9 @@
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="16sp" android:textSize="16sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/check_vibrate" app:layout_constraintBottom_toBottomOf="@+id/check_simple_air"
app:layout_constraintStart_toEndOf="@+id/check_vibrate" app:layout_constraintStart_toEndOf="@+id/check_simple_air"
app:layout_constraintTop_toTopOf="@+id/check_vibrate" /> app:layout_constraintTop_toTopOf="@+id/check_simple_air" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</net.cachapa.expandablelayout.ExpandableLayout> </net.cachapa.expandablelayout.ExpandableLayout>

View File

@ -11,13 +11,13 @@
<string name="collapse"> Control ↑ </string> <string name="collapse"> Control ↑ </string>
<string name="test">Test</string> <string name="test">Test</string>
<string name="service">Service</string> <string name="service">Service</string>
<string name="show_debug_info">Show Debug Info</string> <string name="show_debug_info">Debug</string>
<string name="debug_info">touched: air:%d %s\n%s</string> <string name="debug_info">touched: air:%d %s\n%s</string>
<string name="current_latency">Current Latency: %f ms</string> <string name="current_latency">Current Latency: %f ms</string>
<string name="press_again_to_exit">Press again to exit</string> <string name="press_again_to_exit">Press again to exit</string>
<string name="show_delay">Show Delay</string> <string name="show_delay">Show Delay</string>
<string name="address">Address</string> <string name="address">Address</string>
<string name="enable_vibration">Enable Vibration</string> <string name="enable_vibration">Vibration</string>
<string name="tcp">TCP</string> <string name="tcp">TCP</string>
<string name="udp">UDP</string> <string name="udp">UDP</string>