Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64bdd3d97c | ||
|
|
311fcc9f5d | ||
|
|
3a3e686e4c | ||
|
|
7a53265d8d | ||
|
|
234fcda4f2 | ||
|
|
ccfc1f187e | ||
|
|
9e6a51821c | ||
|
|
3530a25999 | ||
|
|
29aad67e2f | ||
|
|
fb4e41b40e | ||
|
|
84b95e9610 | ||
|
|
95e177f0e5 |
+10
-9
@@ -9,10 +9,10 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.github.brokenithm"
|
||||
minSdkVersion 23
|
||||
minSdkVersion 17
|
||||
targetSdkVersion 30
|
||||
versionCode 10000
|
||||
versionName "1.0.0"
|
||||
versionCode 10300
|
||||
versionName "1.3.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -35,14 +35,15 @@ android {
|
||||
dependencies {
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation 'androidx.core:core-ktx:1.3.2'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'com.google.android.material:material:1.3.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0'
|
||||
implementation 'androidx.core:core-ktx:1.6.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.3.0'
|
||||
implementation 'com.google.android.material:material:1.4.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
|
||||
implementation 'net.cachapa.expandablelayout:expandablelayout:2.9.2'
|
||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<uses-permission android:name="android.permission.NFC"/>
|
||||
|
||||
<application
|
||||
android:name=".BrokenithmApplication"
|
||||
@@ -16,12 +17,17 @@
|
||||
<activity android:name=".activity.MainActivity"
|
||||
android:screenOrientation="landscape"
|
||||
android:configChanges="uiMode|orientation">
|
||||
<meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filters" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.nfc.action.TECH_DISCOVERED" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".activity.SettingsActivity" android:configChanges="uiMode|orientation" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -1,67 +1,89 @@
|
||||
package com.github.brokenithm
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
|
||||
class BrokenithmApplication : Application() {
|
||||
var lastServer: String
|
||||
get() {
|
||||
val config = getSharedPreferences(settings_preference, MODE_PRIVATE)
|
||||
return config.getString("server", "") ?: ""
|
||||
}
|
||||
set(value) {
|
||||
val config = getSharedPreferences(settings_preference, MODE_PRIVATE)
|
||||
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()
|
||||
}
|
||||
abstract class BasePreference<T>(context: Context, fileName: String) {
|
||||
protected val config: SharedPreferences = context.getSharedPreferences(fileName, MODE_PRIVATE)
|
||||
abstract fun value(): T
|
||||
abstract fun update(value: T)
|
||||
}
|
||||
|
||||
var simpleAir: Boolean
|
||||
get() {
|
||||
val config = getSharedPreferences(settings_preference, MODE_PRIVATE)
|
||||
return config.getBoolean("simple_air", false)
|
||||
}
|
||||
set(value) {
|
||||
val config = getSharedPreferences(settings_preference, MODE_PRIVATE)
|
||||
config.edit().putBoolean("simple_air", value).apply()
|
||||
}
|
||||
abstract class Settings<T>(context: Context) : BasePreference<T>(context, settings_preference)
|
||||
|
||||
var showDelay: Boolean
|
||||
get() {
|
||||
val config = getSharedPreferences(settings_preference, MODE_PRIVATE)
|
||||
return config.getBoolean("show_delay", false)
|
||||
}
|
||||
set(value) {
|
||||
val config = getSharedPreferences(settings_preference, MODE_PRIVATE)
|
||||
config.edit().putBoolean("show_delay", value).apply()
|
||||
}
|
||||
open class StringPreference(
|
||||
context: Context,
|
||||
private val key: String,
|
||||
private val defValue: String
|
||||
) : Settings<String>(context) {
|
||||
override fun value() = config.getString(key, defValue) ?: defValue
|
||||
override fun update(value: String) = config.edit().putString(key, value).apply()
|
||||
}
|
||||
|
||||
var enableVibrate: Boolean
|
||||
get() {
|
||||
val config = getSharedPreferences(settings_preference, MODE_PRIVATE)
|
||||
return config.getBoolean("enable_vibrate", true)
|
||||
}
|
||||
set(value) {
|
||||
val config = getSharedPreferences(settings_preference, MODE_PRIVATE)
|
||||
config.edit().putBoolean("enable_vibrate", value).apply()
|
||||
}
|
||||
open class BooleanPreference(
|
||||
context: Context,
|
||||
private val key: String,
|
||||
private val defValue: Boolean
|
||||
) : Settings<Boolean>(context) {
|
||||
override fun value() = config.getBoolean(key, defValue)
|
||||
override fun update(value: Boolean) = config.edit().putBoolean(key, value).apply()
|
||||
}
|
||||
|
||||
var tcpMode: Boolean
|
||||
get() {
|
||||
val config = getSharedPreferences(settings_preference, MODE_PRIVATE)
|
||||
return config.getBoolean("tcp_mode", false)
|
||||
}
|
||||
set(value) {
|
||||
val config = getSharedPreferences(settings_preference, MODE_PRIVATE)
|
||||
config.edit().putBoolean("tcp_mode", value).apply()
|
||||
}
|
||||
open class IntegerPreference(
|
||||
context: Context,
|
||||
private val key: String,
|
||||
private val defValue: Int
|
||||
) : Settings<Int>(context) {
|
||||
override fun value() = config.getInt(key, defValue)
|
||||
override fun update(value: Int) = config.edit().putInt(key, value).apply()
|
||||
}
|
||||
|
||||
open class FloatPreference(
|
||||
context: Context,
|
||||
private val key: String,
|
||||
private val defValue: Float
|
||||
) : Settings<Float>(context) {
|
||||
override fun value() = config.getString(key, defValue.toString())?.toFloat() ?: defValue
|
||||
override fun update(value: Float) = config.edit().putString(key, value.toString()).apply()
|
||||
}
|
||||
|
||||
lateinit var lastServer : StringPreference
|
||||
lateinit var enableAir : BooleanPreference
|
||||
lateinit var airSource : IntegerPreference
|
||||
lateinit var simpleAir : BooleanPreference
|
||||
lateinit var showDelay : BooleanPreference
|
||||
lateinit var enableVibrate : BooleanPreference
|
||||
lateinit var tcpMode : BooleanPreference
|
||||
lateinit var enableNFC : BooleanPreference
|
||||
lateinit var wideTouchRange : BooleanPreference
|
||||
lateinit var enableTouchSize : BooleanPreference
|
||||
lateinit var fatTouchThreshold : FloatPreference
|
||||
lateinit var extraFatTouchThreshold : FloatPreference
|
||||
lateinit var gyroAirLowestBound : FloatPreference
|
||||
lateinit var gyroAirHighestBound : FloatPreference
|
||||
lateinit var accelAirThreshold : FloatPreference
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
lastServer = StringPreference(this, "server", "")
|
||||
enableAir = BooleanPreference(this, "enable_air", true)
|
||||
airSource = IntegerPreference(this, "air_source", 3)
|
||||
simpleAir = BooleanPreference(this, "simple_air", false)
|
||||
showDelay = BooleanPreference(this, "show_delay", false)
|
||||
enableVibrate = BooleanPreference(this, "enable_vibrate", true)
|
||||
tcpMode = BooleanPreference(this, "tcp_mode", false)
|
||||
enableNFC = BooleanPreference(this, "enable_nfc", true)
|
||||
wideTouchRange = BooleanPreference(this, "wide_touch_range", false)
|
||||
enableTouchSize = BooleanPreference(this, "enable_touch_size", false)
|
||||
fatTouchThreshold = FloatPreference(this, "fat_touch_threshold", 0.027f)
|
||||
extraFatTouchThreshold = FloatPreference(this, "extra_fat_touch_threshold", 0.035f)
|
||||
gyroAirLowestBound = FloatPreference(this, "gyro_air_lowest", 0.8f)
|
||||
gyroAirHighestBound = FloatPreference(this, "gyro_air_highest", 1.35f)
|
||||
accelAirThreshold = FloatPreference(this, "accel_air_threshold", 2f)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val settings_preference = "settings"
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
package com.github.brokenithm.activity
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.content.Intent
|
||||
import android.graphics.*
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.hardware.Sensor
|
||||
import android.hardware.SensorEvent
|
||||
import android.hardware.SensorEventListener
|
||||
import android.hardware.SensorManager
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.NfcManager
|
||||
import android.nfc.Tag
|
||||
import android.nfc.tech.MifareClassic
|
||||
import android.os.*
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import android.widget.*
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
@@ -14,11 +22,13 @@ import androidx.lifecycle.lifecycleScope
|
||||
import com.github.brokenithm.BrokenithmApplication
|
||||
import com.github.brokenithm.R
|
||||
import com.github.brokenithm.util.AsyncTaskUtil
|
||||
import com.github.brokenithm.util.FeliCa
|
||||
import net.cachapa.expandablelayout.ExpandableLayout
|
||||
import java.net.*
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.math.abs
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private lateinit var senderTask: AsyncTaskUtil.AsyncTask<InetSocketAddress?, Unit, Unit>
|
||||
@@ -38,11 +48,14 @@ class MainActivity : AppCompatActivity() {
|
||||
private val numOfGaps = 16
|
||||
private val buttonWidthToGap = 7.428571f
|
||||
private val numOfAirBlock = 6
|
||||
private var mEnableTouchSize = false
|
||||
private var mFatTouchSizeThreshold = 0.027f
|
||||
private var mExtraFatTouchSizeThreshold = 0.035f
|
||||
private var mCurrentDelay = 0f
|
||||
|
||||
// Buttons
|
||||
private var mCurrentAirHeight = 6
|
||||
private var mLastButtons = mutableSetOf<Int>()
|
||||
private var mLastButtons = HashSet<Int>()
|
||||
private var mTestButton = 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)
|
||||
@@ -56,6 +69,7 @@ class MainActivity : AppCompatActivity() {
|
||||
private lateinit var mButtonRenderer: View
|
||||
|
||||
// vibrator
|
||||
private var mEnableVibrate = true
|
||||
private lateinit var vibrator: Vibrator
|
||||
private lateinit var vibratorTask: AsyncTaskUtil.AsyncTask<Unit, Unit, Unit>
|
||||
private lateinit var vibrateMethod: (Long) -> Unit
|
||||
@@ -64,13 +78,148 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
// view
|
||||
private var mEnableAir = true
|
||||
private var mAirSource = 3
|
||||
private var mSimpleAir = false
|
||||
private var mDebugInfo = false
|
||||
private var mShowDelay = false
|
||||
private var mEnableVibrate = true
|
||||
private lateinit var mDelayText: TextView
|
||||
private var windowWidth = 0
|
||||
private var windowHeight = 0
|
||||
private var windowWidth = 0f
|
||||
private var windowHeight = 0f
|
||||
private var mTouchAreaRect: Rect? = null
|
||||
|
||||
// sensor
|
||||
private var mSensorManager: SensorManager? = null
|
||||
private var mSensorCallback: ((Float) -> Unit)? = null
|
||||
private var mGyroLowestBound = 0.8f
|
||||
private var mGyroHighestBound = 1.35f
|
||||
private var mAccelThreshold = 2f
|
||||
private val listener = object : SensorEventListener {
|
||||
var current = 0
|
||||
var lastAcceleration = 0f
|
||||
|
||||
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
|
||||
return
|
||||
}
|
||||
|
||||
override fun onSensorChanged(event: SensorEvent?) {
|
||||
event ?: return
|
||||
val threshold = mAccelThreshold
|
||||
when (event.sensor.type) {
|
||||
Sensor.TYPE_LINEAR_ACCELERATION -> {
|
||||
if (mAirSource != 2)
|
||||
return
|
||||
if (lastAcceleration != 0f) {
|
||||
val accelX = (lastAcceleration - event.values[0])
|
||||
if (accelX >= threshold && current >= 0)
|
||||
current --
|
||||
else if (accelX <= -threshold && current <= 0)
|
||||
current ++
|
||||
if (current > 0)
|
||||
mCurrentAirHeight = 0
|
||||
else if (current < 0)
|
||||
mCurrentAirHeight = 6
|
||||
}
|
||||
lastAcceleration = event.values[0]
|
||||
}
|
||||
Sensor.TYPE_ROTATION_VECTOR, Sensor.TYPE_GAME_ROTATION_VECTOR -> {
|
||||
if (mAirSource != 1)
|
||||
return
|
||||
val rotationVector = FloatArray(9)
|
||||
SensorManager.getRotationMatrixFromVector(rotationVector, event.values)
|
||||
val orientation = FloatArray(3)
|
||||
SensorManager.getOrientation(rotationVector, orientation)
|
||||
mSensorCallback?.invoke(orientation[2])
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nfc
|
||||
private fun Byte.getBit(bit: Int) = (toInt() ushr bit) and 0x1
|
||||
private fun MifareClassic.authenticateBlock(blockIndex: Int, keyA: ByteArray, keyB: ByteArray, write: Boolean = false): Boolean {
|
||||
// check access bits
|
||||
val sectorIndex = blockToSector(blockIndex)
|
||||
val accessBitsBlock = sectorToBlock(sectorIndex) + 3
|
||||
if (!authenticateSectorWithKeyA(sectorIndex, keyA)) return false
|
||||
val accessBits = readBlock(accessBitsBlock)
|
||||
val targetBit = blockIndex % 4
|
||||
val bitC1 = accessBits[7].getBit(targetBit + 4)
|
||||
val bitC2 = accessBits[8].getBit(targetBit)
|
||||
val bitC3 = accessBits[8].getBit(targetBit + 4)
|
||||
val allBits = (bitC1 shl 2) or (bitC2 shl 1) or bitC3
|
||||
return if (write) {
|
||||
when (allBits) {
|
||||
0 -> true
|
||||
3, 4, 6 -> authenticateSectorWithKeyB(sectorIndex, keyB)
|
||||
else -> false
|
||||
}
|
||||
} else {
|
||||
when (allBits) {
|
||||
7 -> false
|
||||
3, 5 -> authenticateSectorWithKeyB(sectorIndex, keyB)
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }
|
||||
enum class CardType {
|
||||
CARD_AIME, CARD_FELICA
|
||||
}
|
||||
private var adapter: NfcAdapter? = null
|
||||
private val mAimeKey = byteArrayOf(0x57, 0x43, 0x43, 0x46, 0x76, 0x32)
|
||||
private val mBanaKey = byteArrayOf(0x60, -0x70, -0x30, 0x06, 0x32, -0x0b)
|
||||
private var mEnableNFC = true
|
||||
private var hasCard = false
|
||||
private var cardType = CardType.CARD_AIME
|
||||
private val cardId = ByteArray(10)
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
val tag: Tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG) ?: return
|
||||
val felica = FeliCa.get(tag)
|
||||
if (felica != null) {
|
||||
thread {
|
||||
try {
|
||||
felica.connect()
|
||||
felica.poll()
|
||||
felica.IDm?.copyInto(cardId) ?: throw IllegalStateException("Failed to fetch IDm from FeliCa")
|
||||
cardId[8] = 0
|
||||
cardId[9] = 0
|
||||
cardType = CardType.CARD_FELICA
|
||||
hasCard = true
|
||||
Log.d(TAG, "Found FeliCa card: ${cardId.toHexString().removeRange(16..19)}")
|
||||
while (felica.isConnected) Thread.sleep(50)
|
||||
hasCard = false
|
||||
felica.close()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
val mifare = MifareClassic.get(tag) ?: return
|
||||
thread {
|
||||
try {
|
||||
mifare.connect()
|
||||
if (mifare.authenticateBlock(2, keyA = mAimeKey, keyB = mAimeKey) ||
|
||||
mifare.authenticateBlock(2, keyA = mBanaKey, keyB = mAimeKey)) {
|
||||
Thread.sleep(100)
|
||||
val block = mifare.readBlock(2)
|
||||
block.copyInto(cardId, 0, 6, 16)
|
||||
cardType = CardType.CARD_AIME
|
||||
hasCard = true
|
||||
Log.d(TAG, "Found Aime card: ${cardId.toHexString()}")
|
||||
while (mifare.isConnected) Thread.sleep(50)
|
||||
hasCard = false
|
||||
} else {
|
||||
Log.d(TAG, "NFC auth failed")
|
||||
}
|
||||
mifare.close()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -79,6 +228,8 @@ class MainActivity : AppCompatActivity() {
|
||||
setImmersive()
|
||||
app = application as BrokenithmApplication
|
||||
vibrator = applicationContext.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||
val nfcManager = getSystemService(Context.NFC_SERVICE) as NfcManager
|
||||
adapter = nfcManager.defaultAdapter
|
||||
|
||||
vibrateMethod = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
{
|
||||
@@ -90,125 +241,13 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
val settings = findViewById<Button>(R.id.button_settings)
|
||||
settings.setOnClickListener { startActivity(Intent(this, SettingsActivity::class.java)) }
|
||||
|
||||
mDelayText = findViewById(R.id.text_delay)
|
||||
val textInfo = findViewById<TextView>(R.id.text_info)
|
||||
findViewById<CheckBox>(R.id.check_debug).setOnCheckedChangeListener { _, isChecked ->
|
||||
mDebugInfo = isChecked
|
||||
textInfo.visibility = if (isChecked) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
}
|
||||
findViewById<CheckBox>(R.id.check_vibrate).apply {
|
||||
setOnCheckedChangeListener { _, isChecked ->
|
||||
mEnableVibrate = isChecked
|
||||
app.enableVibrate = isChecked
|
||||
}
|
||||
isChecked = app.enableVibrate
|
||||
}
|
||||
|
||||
val expandControl = findViewById<ExpandableLayout>(R.id.expand_control)
|
||||
val textExpand = findViewById<TextView>(R.id.text_expand)
|
||||
textExpand.setOnClickListener {
|
||||
if (expandControl.isExpanded) {
|
||||
(it as TextView).setText(R.string.expand)
|
||||
expandControl.collapse()
|
||||
} else {
|
||||
(it as TextView).setText(R.string.collapse)
|
||||
expandControl.expand()
|
||||
}
|
||||
}
|
||||
|
||||
val dm = DisplayMetrics()
|
||||
(applicationContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.getMetrics(dm)
|
||||
windowWidth = dm.widthPixels
|
||||
windowHeight = dm.heightPixels
|
||||
gapWidth = windowWidth.toFloat() / (numOfButtons * buttonWidthToGap + numOfGaps)
|
||||
buttonWidth = gapWidth * buttonWidthToGap
|
||||
//val buttonWidth = windowWidth / numOfButtons
|
||||
val buttonBlockWidth = buttonWidth + gapWidth
|
||||
val buttonAreaHeight = windowHeight * 0.5f
|
||||
val airAreaHeight = windowHeight * 0.35f
|
||||
val airBlockHeight = (buttonAreaHeight - airAreaHeight) / numOfAirBlock
|
||||
|
||||
mLEDBitmap = Bitmap.createBitmap(windowWidth, buttonAreaHeight.toInt(), Bitmap.Config.RGB_565)
|
||||
mLEDCanvas = Canvas(mLEDBitmap)
|
||||
mButtonRenderer = findViewById(R.id.button_render_area)
|
||||
mButtonRenderer.background = BitmapDrawable(resources, mLEDBitmap)
|
||||
|
||||
findViewById<View>(R.id.touch_area).setOnTouchListener { view, event ->
|
||||
if (expandControl.isExpanded)
|
||||
textExpand.callOnClick()
|
||||
view ?: return@setOnTouchListener view.performClick()
|
||||
event ?: return@setOnTouchListener view.performClick()
|
||||
val totalTouches = event.pointerCount
|
||||
val touchedButtons = mutableSetOf<Int>()
|
||||
var thisAirHeight = 6
|
||||
if (event.action != KeyEvent.ACTION_UP && event.action != MotionEvent.ACTION_CANCEL) {
|
||||
var ignoredIndex = -1
|
||||
if (event.actionMasked == MotionEvent.ACTION_POINTER_UP)
|
||||
ignoredIndex = event.actionIndex
|
||||
for (i in 0 until totalTouches) {
|
||||
if (i == ignoredIndex)
|
||||
continue
|
||||
val x = event.getX(i) + view.left
|
||||
val y = event.getY(i) + view.top
|
||||
when(y) {
|
||||
in 0f..airAreaHeight -> {
|
||||
thisAirHeight = 0
|
||||
}
|
||||
in airAreaHeight..buttonAreaHeight -> {
|
||||
val curAir = ((y - airAreaHeight) / airBlockHeight).toInt()
|
||||
thisAirHeight = if(mSimpleAir) 0 else thisAirHeight.coerceAtMost(curAir)
|
||||
}
|
||||
in buttonAreaHeight..windowHeight.toFloat() -> {
|
||||
//val centerButton = (x / buttonBlockWidth).toInt() + 1
|
||||
//val leftButton = (centerButton - 1).coerceAtLeast(1)
|
||||
//val rightButton = (centerButton + 1).coerceAtMost(32)
|
||||
//touchedButtons.addAll(listOf(leftButton, centerButton, rightButton))
|
||||
//touchedButtons.addAll(listOf((centerButton * 2 - 1), centerButton * 2))
|
||||
val pointPos = x / buttonBlockWidth
|
||||
var index = pointPos.toInt()
|
||||
if (index > numOfButtons) index = numOfButtons
|
||||
var realIndex = index * 2
|
||||
if (touchedButtons.contains(realIndex)) realIndex++
|
||||
touchedButtons.add(realIndex)
|
||||
if (index > 0) {
|
||||
if ((pointPos - index) * 4 < 1) {
|
||||
realIndex = (index - 1) * 2
|
||||
if (touchedButtons.contains(realIndex)) realIndex++
|
||||
touchedButtons.add(realIndex)
|
||||
}
|
||||
} else if (index < 31) {
|
||||
if ((pointPos - index) * 4 > 3) {
|
||||
realIndex = (index + 1) * 2
|
||||
if (touchedButtons.contains(realIndex)) realIndex++
|
||||
touchedButtons.add(realIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
thisAirHeight = 6
|
||||
if (mEnableVibrate) {
|
||||
if (hasNewKeys(mLastButtons, touchedButtons))
|
||||
mVibrationQueue.add(vibrateLength)
|
||||
else if (touchedButtons.isEmpty())
|
||||
mVibrationQueue.clear()
|
||||
}
|
||||
mLastButtons = touchedButtons
|
||||
mCurrentAirHeight = thisAirHeight
|
||||
//mInputQueue.add(InputEvent(touchedButtons, mCurrentAirHeight))
|
||||
if (mDebugInfo)
|
||||
textInfo.text = getString(R.string.debug_info, mCurrentAirHeight, touchedButtons.toString(), event.toString())
|
||||
view.performClick()
|
||||
}
|
||||
|
||||
val editServer = findViewById<EditText>(R.id.edit_server).apply {
|
||||
setText(app.lastServer)
|
||||
setText(app.lastServer.value())
|
||||
}
|
||||
findViewById<Button>(R.id.button_start).setOnClickListener {
|
||||
val server = editServer.text.toString()
|
||||
@@ -220,8 +259,9 @@ class MainActivity : AppCompatActivity() {
|
||||
mExitFlag = false
|
||||
(it as Button).setText(R.string.stop)
|
||||
editServer.isEnabled = false
|
||||
settings.isEnabled = false
|
||||
|
||||
app.lastServer = server
|
||||
app.lastServer.update(server)
|
||||
val address = parseAddress(server)
|
||||
if (!mTCPMode)
|
||||
sendConnect(address)
|
||||
@@ -234,6 +274,7 @@ class MainActivity : AppCompatActivity() {
|
||||
mExitFlag = true
|
||||
(it as Button).setText(R.string.start)
|
||||
editServer.isEnabled = true
|
||||
settings.isEnabled = true
|
||||
senderTask.cancel()
|
||||
receiverTask.cancel()
|
||||
pingPongTask.cancel()
|
||||
@@ -250,23 +291,51 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
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
|
||||
mAirSource = app.airSource.value()
|
||||
findViewById<TextView>(R.id.text_switch_air).apply {
|
||||
setOnClickListener {
|
||||
mAirSource = when (mAirSource) {
|
||||
0 -> {
|
||||
text = getString(R.string.gyro_air)
|
||||
mEnableAir = true
|
||||
checkSimpleAir.isEnabled = true
|
||||
1
|
||||
}
|
||||
1 -> {
|
||||
text = getString(R.string.accel_air)
|
||||
checkSimpleAir.isEnabled = false
|
||||
2
|
||||
}
|
||||
2 -> {
|
||||
text = getString(R.string.touch_air)
|
||||
checkSimpleAir.isEnabled = true
|
||||
3
|
||||
}
|
||||
else -> {
|
||||
text = getString(R.string.disable_air)
|
||||
checkSimpleAir.isEnabled = false
|
||||
mEnableAir = false
|
||||
0
|
||||
}
|
||||
}
|
||||
app.airSource.update(mAirSource)
|
||||
}
|
||||
isChecked = app.enableAir
|
||||
text = getString(when (mAirSource) {
|
||||
0 -> { checkSimpleAir.isEnabled = false; R.string.disable_air }
|
||||
1 -> { checkSimpleAir.isEnabled = true; R.string.gyro_air }
|
||||
2 -> { checkSimpleAir.isEnabled = false; R.string.accel_air }
|
||||
else -> { checkSimpleAir.isEnabled = true; R.string.touch_air }
|
||||
})
|
||||
}
|
||||
checkSimpleAir.apply {
|
||||
setOnCheckedChangeListener { _, isChecked ->
|
||||
mSimpleAir = isChecked
|
||||
app.simpleAir = isChecked
|
||||
app.simpleAir.update(isChecked)
|
||||
}
|
||||
isChecked = app.simpleAir
|
||||
isChecked = app.simpleAir.value()
|
||||
}
|
||||
mEnableAir = app.enableAir
|
||||
mSimpleAir = app.simpleAir
|
||||
mEnableAir = app.enableAir.value()
|
||||
mSimpleAir = app.simpleAir.value()
|
||||
|
||||
findViewById<View>(R.id.button_test).setOnTouchListener { view, event ->
|
||||
mTestButton = when(event.actionMasked) {
|
||||
@@ -289,12 +358,12 @@ class MainActivity : AppCompatActivity() {
|
||||
setOnCheckedChangeListener { _, isChecked ->
|
||||
mShowDelay = isChecked
|
||||
mDelayText.visibility = if (isChecked) View.VISIBLE else View.GONE
|
||||
app.showDelay = isChecked
|
||||
app.showDelay.update(isChecked)
|
||||
}
|
||||
isChecked = app.showDelay
|
||||
isChecked = app.showDelay.value()
|
||||
}
|
||||
|
||||
mTCPMode = app.tcpMode
|
||||
mTCPMode = app.tcpMode.value()
|
||||
findViewById<TextView>(R.id.text_mode).apply {
|
||||
text = getString(if (mTCPMode) R.string.tcp else R.string.udp)
|
||||
setOnClickListener {
|
||||
@@ -307,7 +376,7 @@ class MainActivity : AppCompatActivity() {
|
||||
mTCPMode = true
|
||||
R.string.tcp
|
||||
})
|
||||
app.tcpMode = mTCPMode
|
||||
app.tcpMode.update(mTCPMode)
|
||||
}
|
||||
}
|
||||
initTasks()
|
||||
@@ -315,6 +384,249 @@ class MainActivity : AppCompatActivity() {
|
||||
//mButtons.add(findViewById(id))
|
||||
|
||||
vibratorTask.execute(lifecycleScope)
|
||||
|
||||
mSensorCallback = {
|
||||
val lowest = mGyroLowestBound
|
||||
val highest = mGyroHighestBound
|
||||
val steps = (highest - lowest) / numOfAirBlock
|
||||
val current = abs(it)
|
||||
mCurrentAirHeight = if (mSimpleAir) {
|
||||
when (current) {
|
||||
in 0f..lowest -> 6
|
||||
else -> 0
|
||||
}
|
||||
} else {
|
||||
when (current) {
|
||||
in 0f..lowest -> 6
|
||||
in lowest..highest -> {
|
||||
((highest - current) / steps).toInt()
|
||||
}
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val contentView = findViewById<ViewGroup>(android.R.id.content)
|
||||
contentView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
|
||||
override fun onGlobalLayout() {
|
||||
contentView.viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||
/*
|
||||
val dm = DisplayMetrics()
|
||||
(applicationContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.getRealMetrics(dm)
|
||||
windowWidth = dm.widthPixels.toFloat()
|
||||
windowHeight = dm.heightPixels.toFloat()*/
|
||||
/*
|
||||
val point = Point()
|
||||
(application.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.getRealSize(point)
|
||||
windowWidth = point.x.toFloat()
|
||||
windowHeight = point.y.toFloat()*/
|
||||
val arr = IntArray(2)
|
||||
contentView.getLocationOnScreen(arr)
|
||||
windowWidth = contentView.width.toFloat()
|
||||
windowHeight = contentView.height.toFloat()
|
||||
initTouchArea(arr[0], arr[1])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun initTouchArea(windowLeft: Int, windowTop: Int) {
|
||||
val expandControl = findViewById<ExpandableLayout>(R.id.expand_control)
|
||||
val textExpand = findViewById<TextView>(R.id.text_expand)
|
||||
textExpand.setOnClickListener {
|
||||
if (expandControl.isExpanded) {
|
||||
(it as TextView).setText(R.string.expand)
|
||||
expandControl.collapse()
|
||||
} else {
|
||||
(it as TextView).setText(R.string.collapse)
|
||||
expandControl.expand()
|
||||
}
|
||||
}
|
||||
|
||||
val textInfo = findViewById<TextView>(R.id.text_info)
|
||||
findViewById<CheckBox>(R.id.check_debug).setOnCheckedChangeListener { _, isChecked ->
|
||||
mDebugInfo = isChecked
|
||||
textInfo.visibility = if (isChecked) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
gapWidth = windowWidth / (numOfButtons * buttonWidthToGap + numOfGaps)
|
||||
buttonWidth = gapWidth * buttonWidthToGap
|
||||
//val buttonWidth = windowWidth / numOfButtons
|
||||
val buttonBlockWidth = buttonWidth + gapWidth
|
||||
val buttonAreaHeight = windowHeight * 0.5f
|
||||
val airAreaHeight = windowHeight * 0.35f
|
||||
val airBlockHeight = (buttonAreaHeight - airAreaHeight) / numOfAirBlock
|
||||
|
||||
mLEDBitmap = Bitmap.createBitmap(windowWidth.toInt(), buttonAreaHeight.toInt(), Bitmap.Config.RGB_565)
|
||||
mLEDCanvas = Canvas(mLEDBitmap)
|
||||
mButtonRenderer = findViewById(R.id.button_render_area)
|
||||
mButtonRenderer.background = BitmapDrawable(resources, mLEDBitmap)
|
||||
|
||||
findViewById<View>(R.id.touch_area).setOnTouchListener { view, event ->
|
||||
if (expandControl.isExpanded)
|
||||
textExpand.callOnClick()
|
||||
view ?: return@setOnTouchListener view.performClick()
|
||||
event ?: return@setOnTouchListener view.performClick()
|
||||
if (mTouchAreaRect == null) {
|
||||
val arr = IntArray(2)
|
||||
view.getLocationOnScreen(arr)
|
||||
mTouchAreaRect = Rect(arr[0], arr[1], arr[0] + view.width, arr[1] + view.height)
|
||||
}
|
||||
val currentAirAreaHeight = if (mAirSource != 3) 0f else airAreaHeight
|
||||
val currentButtonAreaHeight = if (mAirSource != 3) 0f else buttonAreaHeight
|
||||
val totalTouches = event.pointerCount
|
||||
val touchedButtons = HashSet<Int>()
|
||||
var thisAirHeight = 6
|
||||
var maxTouchedSize = 0f
|
||||
if (event.action != KeyEvent.ACTION_UP && event.action != MotionEvent.ACTION_CANCEL) {
|
||||
var ignoredIndex = -1
|
||||
if (event.actionMasked == MotionEvent.ACTION_POINTER_UP)
|
||||
ignoredIndex = event.actionIndex
|
||||
for (i in 0 until totalTouches) {
|
||||
if (i == ignoredIndex)
|
||||
continue
|
||||
val x = event.getX(i) + mTouchAreaRect!!.left - windowLeft
|
||||
val y = event.getY(i) + mTouchAreaRect!!.top - windowTop
|
||||
when(y) {
|
||||
in 0f..currentAirAreaHeight -> {
|
||||
thisAirHeight = 0
|
||||
}
|
||||
in currentAirAreaHeight..currentButtonAreaHeight -> {
|
||||
val curAir = ((y - airAreaHeight) / airBlockHeight).toInt()
|
||||
thisAirHeight = if(mSimpleAir) 0 else thisAirHeight.coerceAtMost(curAir)
|
||||
}
|
||||
in currentButtonAreaHeight..windowHeight -> {
|
||||
val pointPos = x / buttonBlockWidth
|
||||
var index = pointPos.toInt()
|
||||
if (index > numOfButtons) index = numOfButtons
|
||||
|
||||
if (mEnableTouchSize) {
|
||||
var centerButton = index * 2
|
||||
if (touchedButtons.contains(centerButton)) centerButton++
|
||||
var leftButton = ((index - 1) * 2).coerceAtLeast(0)
|
||||
if (touchedButtons.contains(leftButton)) leftButton++
|
||||
var rightButton = ((index + 1) * 2).coerceAtMost(numOfButtons * 2)
|
||||
if (touchedButtons.contains(rightButton)) rightButton++
|
||||
var left2Button = ((index - 2) * 2).coerceAtLeast(0)
|
||||
if (touchedButtons.contains(left2Button)) left2Button++
|
||||
var right2Button = ((index + 2) * 2).coerceAtMost(numOfButtons * 2)
|
||||
if (touchedButtons.contains(right2Button)) right2Button++
|
||||
|
||||
val currentSize = event.getSize(i)
|
||||
maxTouchedSize = maxTouchedSize.coerceAtLeast(currentSize)
|
||||
|
||||
touchedButtons.add(centerButton)
|
||||
when ((pointPos - index) * 4) {
|
||||
in 0f..1f -> {
|
||||
touchedButtons.add(leftButton)
|
||||
if (currentSize >= mExtraFatTouchSizeThreshold) {
|
||||
touchedButtons.add(left2Button)
|
||||
touchedButtons.add(rightButton)
|
||||
}
|
||||
}
|
||||
in 1f..3f -> {
|
||||
if (currentSize >= mFatTouchSizeThreshold) {
|
||||
touchedButtons.add(leftButton)
|
||||
touchedButtons.add(rightButton)
|
||||
}
|
||||
if (currentSize >= mExtraFatTouchSizeThreshold) {
|
||||
touchedButtons.add(left2Button)
|
||||
touchedButtons.add(right2Button)
|
||||
}
|
||||
}
|
||||
in 3f..4f -> {
|
||||
touchedButtons.add(rightButton)
|
||||
if (currentSize >= mExtraFatTouchSizeThreshold) {
|
||||
touchedButtons.add(leftButton)
|
||||
touchedButtons.add(right2Button)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (index > 15) index = 15
|
||||
var targetIndex = index * 2
|
||||
if (touchedButtons.contains(targetIndex)) targetIndex++
|
||||
touchedButtons.add(targetIndex)
|
||||
if (index > 0) {
|
||||
if ((pointPos - index) * 4 < 1) {
|
||||
targetIndex = (index - 1) * 2
|
||||
if (touchedButtons.contains(targetIndex)) targetIndex++
|
||||
touchedButtons.add(targetIndex)
|
||||
}
|
||||
} else if (index < 31) {
|
||||
if ((pointPos - index) * 4 > 3) {
|
||||
targetIndex = (index + 1) * 2
|
||||
if (touchedButtons.contains(targetIndex)) targetIndex++
|
||||
touchedButtons.add(targetIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
thisAirHeight = 6
|
||||
if (mEnableVibrate) {
|
||||
if (hasNewKeys(mLastButtons, touchedButtons))
|
||||
mVibrationQueue.add(vibrateLength)
|
||||
else if (touchedButtons.isEmpty())
|
||||
mVibrationQueue.clear()
|
||||
}
|
||||
mLastButtons = touchedButtons
|
||||
if (mAirSource == 3)
|
||||
mCurrentAirHeight = thisAirHeight
|
||||
//mInputQueue.add(InputEvent(touchedButtons, mCurrentAirHeight))
|
||||
if (mDebugInfo)
|
||||
textInfo.text = getString(R.string.debug_info, mCurrentAirHeight, touchedButtons.toString(), maxTouchedSize, event.toString())
|
||||
view.performClick()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (mSensorManager == null)
|
||||
mSensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
||||
val gyro = mSensorManager?.getDefaultSensor(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) Sensor.TYPE_GAME_ROTATION_VECTOR else Sensor.TYPE_ROTATION_VECTOR)
|
||||
mSensorManager?.registerListener(listener, gyro, 10000)
|
||||
val accel = mSensorManager?.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION)
|
||||
mSensorManager?.registerListener(listener, accel, 10000)
|
||||
enableNfcForegroundDispatch()
|
||||
loadPreference()
|
||||
}
|
||||
|
||||
private fun loadPreference() {
|
||||
mEnableTouchSize = app.enableTouchSize.value()
|
||||
mFatTouchSizeThreshold = app.fatTouchThreshold.value()
|
||||
mExtraFatTouchSizeThreshold = app.extraFatTouchThreshold.value()
|
||||
mEnableNFC = app.enableNFC.value()
|
||||
mEnableVibrate = app.enableVibrate.value()
|
||||
mGyroLowestBound = app.gyroAirLowestBound.value()
|
||||
mGyroHighestBound = app.gyroAirHighestBound.value()
|
||||
mAccelThreshold = app.accelAirThreshold.value()
|
||||
}
|
||||
|
||||
private fun enableNfcForegroundDispatch() {
|
||||
try {
|
||||
val intent = Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||
val nfcPendingIntent = PendingIntent.getActivity(this, 0, intent, 0)
|
||||
adapter?.enableForegroundDispatch(this, nfcPendingIntent, null, null)
|
||||
} catch (ex: IllegalStateException) {
|
||||
Log.e(TAG, "Error enabling NFC foreground dispatch", ex)
|
||||
}
|
||||
}
|
||||
|
||||
private fun disableNfcForegroundDispatch() {
|
||||
try {
|
||||
adapter?.disableForegroundDispatch(this)
|
||||
} catch (ex: IllegalStateException) {
|
||||
Log.e(TAG, "Error disabling NFC foreground dispatch", ex)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
disableNfcForegroundDispatch()
|
||||
mSensorManager?.unregisterListener(listener)
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
@@ -366,6 +678,7 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun Char.byte() = code.toByte()
|
||||
private fun initTasks() {
|
||||
receiverTask = AsyncTaskUtil.AsyncTask.make(
|
||||
doInBackground = {
|
||||
@@ -380,10 +693,10 @@ class MainActivity : AppCompatActivity() {
|
||||
try {
|
||||
val dataSize = mTCPSocket.getInputStream().read(buffer, 0, 256)
|
||||
if (dataSize >= 3) {
|
||||
if (dataSize >= 100 && buffer[1] == 'L'.toByte() && buffer[2] == 'E'.toByte() && buffer[3] == 'D'.toByte()) {
|
||||
if (dataSize >= 100 && buffer[1] == 'L'.byte() && buffer[2] == 'E'.byte() && buffer[3] == 'D'.byte()) {
|
||||
setLED(buffer)
|
||||
}
|
||||
if (dataSize >= 4 && buffer[1] == 'P'.toByte() && buffer[2] == 'O'.toByte() && buffer[3] == 'N'.toByte()) {
|
||||
if (dataSize >= 4 && buffer[1] == 'P'.byte() && buffer[2] == 'O'.byte() && buffer[3] == 'N'.byte()) {
|
||||
val delay = calculateDelay(buffer)
|
||||
if (delay > 0f)
|
||||
mCurrentDelay = delay
|
||||
@@ -405,16 +718,23 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
val buffer = ByteArray(256)
|
||||
val packet = DatagramPacket(buffer, buffer.size)
|
||||
fun InetSocketAddress.toHostString(): String? {
|
||||
if (hostName != null)
|
||||
return hostName
|
||||
if (this.address != null)
|
||||
return this.address.hostName ?: this.address.hostAddress
|
||||
return null
|
||||
}
|
||||
while (!mExitFlag) {
|
||||
try {
|
||||
socket.receive(packet)
|
||||
if (packet.address.hostAddress == address.hostString && packet.port == address.port) {
|
||||
if (packet.address.hostAddress == address.toHostString() && packet.port == address.port) {
|
||||
val data = packet.data
|
||||
if (data.size >= 3) {
|
||||
if (data.size >= 100 && data[1] == 'L'.toByte() && data[2] == 'E'.toByte() && data[3] == 'D'.toByte()) {
|
||||
if (data.size >= 100 && data[1] == 'L'.byte() && data[2] == 'E'.byte() && data[3] == 'D'.byte()) {
|
||||
setLED(data)
|
||||
}
|
||||
if (data.size >= 4 && data[1] == 'P'.toByte() && data[2] == 'O'.toByte() && data[3] == 'N'.toByte()) {
|
||||
if (data.size >= 4 && data[1] == 'P'.byte() && data[2] == 'O'.byte() && data[3] == 'N'.byte()) {
|
||||
val delay = calculateDelay(data)
|
||||
if (delay > 0f)
|
||||
mCurrentDelay = delay
|
||||
@@ -433,8 +753,15 @@ class MainActivity : AppCompatActivity() {
|
||||
doInBackground = {
|
||||
val address = it[0] ?: return@make
|
||||
if (mTCPMode) {
|
||||
mTCPSocket = Socket()
|
||||
mTCPSocket.connect(address)
|
||||
try {
|
||||
mTCPSocket = Socket().apply {
|
||||
tcpNoDelay = true
|
||||
}
|
||||
mTCPSocket.connect(address)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return@make
|
||||
}
|
||||
while (!mExitFlag) {
|
||||
if (mShowDelay)
|
||||
sendTCPPing()
|
||||
@@ -442,6 +769,8 @@ class MainActivity : AppCompatActivity() {
|
||||
val buffer = applyKeys(buttons, IoBuffer())
|
||||
try {
|
||||
mTCPSocket.getOutputStream().write(constructBuffer(buffer))
|
||||
if (mEnableNFC)
|
||||
mTCPSocket.getOutputStream().write(constructCardData())
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
continue
|
||||
@@ -450,26 +779,37 @@ class MainActivity : AppCompatActivity() {
|
||||
Thread.sleep(1)
|
||||
}
|
||||
} else {
|
||||
val socket = DatagramSocket()
|
||||
socket.connect(address)
|
||||
val socket = try {
|
||||
DatagramSocket().apply {
|
||||
reuseAddress = true
|
||||
soTimeout = 1000
|
||||
}
|
||||
} catch (e: BindException) {
|
||||
e.printStackTrace()
|
||||
return@make
|
||||
}
|
||||
try {
|
||||
socket.connect(address)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return@make
|
||||
}
|
||||
while (!mExitFlag) {
|
||||
if (mShowDelay)
|
||||
sendPing(address)
|
||||
//while (!mInputQueue.isEmpty() && mInputQueue.peek() == null)
|
||||
//mInputQueue.pop()
|
||||
//val buttons = mInputQueue.poll()
|
||||
val buttons = InputEvent(mLastButtons, mCurrentAirHeight, mTestButton, mServiceButton)
|
||||
if (buttons != null/* || mLastAirHeight != mCurrentAirHeight*/) {
|
||||
val buffer = applyKeys(buttons/* ?: InputEvent()*/, IoBuffer())
|
||||
val packet = constructPacket(buffer)
|
||||
try {
|
||||
socket.send(packet)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Thread.sleep(100)
|
||||
continue
|
||||
}
|
||||
val buffer = applyKeys(buttons/* ?: InputEvent()*/, IoBuffer())
|
||||
val packet = constructPacket(buffer)
|
||||
try {
|
||||
socket.send(packet)
|
||||
if (mEnableNFC)
|
||||
socket.send(constructCardPacket())
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Thread.sleep(100)
|
||||
continue
|
||||
}
|
||||
//Thread.sleep(2)
|
||||
Thread.sleep(1)
|
||||
}
|
||||
socket.close()
|
||||
@@ -506,6 +846,7 @@ class MainActivity : AppCompatActivity() {
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
enum class FunctionButton {
|
||||
UNDEFINED, FUNCTION_COIN, FUNCTION_CARD
|
||||
}
|
||||
@@ -519,7 +860,7 @@ class MainActivity : AppCompatActivity() {
|
||||
var serviceBtn = false
|
||||
}
|
||||
|
||||
private fun getLocalIPAddress(useIPv4: Boolean): ByteArray {
|
||||
private fun getLocalIPAddress(useIPv4: Boolean = true): ByteArray {
|
||||
try {
|
||||
val interfaces: List<NetworkInterface> = Collections.list(NetworkInterface.getNetworkInterfaces())
|
||||
for (intf in interfaces) {
|
||||
@@ -543,10 +884,10 @@ class MainActivity : AppCompatActivity() {
|
||||
private fun sendConnect(address: InetSocketAddress?) {
|
||||
address ?: return
|
||||
thread {
|
||||
val selfAddress = getLocalIPAddress(true)
|
||||
val selfAddress = getLocalIPAddress()
|
||||
if (selfAddress.isEmpty()) return@thread
|
||||
val buffer = ByteArray(21)
|
||||
byteArrayOf('C'.toByte(), 'O'.toByte(), 'N'.toByte()).copyInto(buffer, 1)
|
||||
byteArrayOf('C'.byte(), 'O'.byte(), 'N'.byte()).copyInto(buffer, 1)
|
||||
ByteBuffer.wrap(buffer)
|
||||
.put(4, if (selfAddress.size == 4) 1.toByte() else 2.toByte())
|
||||
.putShort(5, serverPort.toShort())
|
||||
@@ -569,7 +910,7 @@ class MainActivity : AppCompatActivity() {
|
||||
private fun sendDisconnect(address: InetSocketAddress?) {
|
||||
address ?: return
|
||||
thread {
|
||||
val buffer = byteArrayOf(3, 'D'.toByte(), 'I'.toByte(), 'S'.toByte())
|
||||
val buffer = byteArrayOf(3, 'D'.byte(), 'I'.byte(), 'S'.byte())
|
||||
if (mTCPMode) {
|
||||
try {
|
||||
mTCPSocket.getOutputStream().write(buffer)
|
||||
@@ -596,7 +937,7 @@ class MainActivity : AppCompatActivity() {
|
||||
private fun sendFunctionKey(address: InetSocketAddress?, function: FunctionButton) {
|
||||
address ?: return
|
||||
thread {
|
||||
val buffer = byteArrayOf(4, 'F'.toByte(), 'N'.toByte(), 'C'.toByte(), function.ordinal.toByte())
|
||||
val buffer = byteArrayOf(4, 'F'.byte(), 'N'.byte(), 'C'.byte(), function.ordinal.toByte())
|
||||
if (mTCPMode) {
|
||||
try {
|
||||
mTCPSocket.getOutputStream().write(buffer)
|
||||
@@ -626,7 +967,7 @@ class MainActivity : AppCompatActivity() {
|
||||
if (System.currentTimeMillis() - lastPingTime < pingInterval) return
|
||||
lastPingTime = System.currentTimeMillis()
|
||||
val buffer = ByteArray(12)
|
||||
byteArrayOf(11, 'P'.toByte(), 'I'.toByte(), 'N'.toByte()).copyInto(buffer)
|
||||
byteArrayOf(11, 'P'.byte(), 'I'.byte(), 'N'.byte()).copyInto(buffer)
|
||||
ByteBuffer.wrap(buffer, 4, 8).putLong(SystemClock.elapsedRealtimeNanos())
|
||||
try {
|
||||
val socket = DatagramSocket()
|
||||
@@ -645,7 +986,7 @@ class MainActivity : AppCompatActivity() {
|
||||
if (System.currentTimeMillis() - lastPingTime < pingInterval) return
|
||||
lastPingTime = System.currentTimeMillis()
|
||||
val buffer = ByteArray(12)
|
||||
byteArrayOf(11, 'P'.toByte(), 'I'.toByte(), 'N'.toByte()).copyInto(buffer)
|
||||
byteArrayOf(11, 'P'.byte(), 'I'.byte(), 'N'.byte()).copyInto(buffer)
|
||||
ByteBuffer.wrap(buffer, 4, 8).putLong(SystemClock.elapsedRealtimeNanos())
|
||||
try {
|
||||
mTCPSocket.getOutputStream().write(buffer)
|
||||
@@ -672,7 +1013,7 @@ class MainActivity : AppCompatActivity() {
|
||||
realBuf[46] = if (buffer.testBtn) 0x01 else 0x00
|
||||
realBuf[47] = if (buffer.serviceBtn) 0x01 else 0x00
|
||||
} else {
|
||||
buffer.slider.copyInto(realBuf, 10)
|
||||
buffer.slider.copyInto(realBuf, 8)
|
||||
realBuf[40] = if (buffer.testBtn) 0x01 else 0x00
|
||||
realBuf[41] = if (buffer.serviceBtn) 0x01 else 0x00
|
||||
}
|
||||
@@ -684,6 +1025,21 @@ class MainActivity : AppCompatActivity() {
|
||||
return DatagramPacket(realBuf, buffer.length + 1)
|
||||
}
|
||||
|
||||
private fun constructCardData(): ByteArray {
|
||||
val buf = ByteArray(24)
|
||||
byteArrayOf(15, 'C'.byte(), 'R'.byte(), 'D'.byte()).copyInto(buf)
|
||||
buf[4] = if (hasCard) 1 else 0
|
||||
buf[5] = cardType.ordinal.toByte()
|
||||
if (hasCard)
|
||||
cardId.copyInto(buf, 6)
|
||||
return buf
|
||||
}
|
||||
|
||||
private fun constructCardPacket(): DatagramPacket {
|
||||
val buf = constructCardData()
|
||||
return DatagramPacket(buf, buf[0] + 1)
|
||||
}
|
||||
|
||||
private val airUpdateInterval = 10L
|
||||
private var mLastAirHeight = 6
|
||||
private var mLastAirUpdateTime = 0L
|
||||
@@ -691,10 +1047,10 @@ class MainActivity : AppCompatActivity() {
|
||||
return buffer.apply {
|
||||
if (mEnableAir) {
|
||||
buffer.length = 47
|
||||
buffer.header = byteArrayOf('I'.toByte(), 'N'.toByte(), 'P'.toByte())
|
||||
buffer.header = byteArrayOf('I'.byte(), 'N'.byte(), 'P'.byte())
|
||||
} else {
|
||||
buffer.length = 41
|
||||
buffer.header = byteArrayOf('I'.toByte(), 'P'.toByte(), 'T'.toByte())
|
||||
buffer.header = byteArrayOf('I'.byte(), 'P'.byte(), 'T'.byte())
|
||||
}
|
||||
|
||||
if (event.keys != null && event.keys.isNotEmpty()) {
|
||||
@@ -743,12 +1099,14 @@ class MainActivity : AppCompatActivity() {
|
||||
else -> continue
|
||||
}
|
||||
val right = left + width
|
||||
mLEDCanvas.drawRect(left, 0f, right, drawHeight.toFloat(), makePaint(color.toInt()))
|
||||
mLEDCanvas.drawRect(left, 0f, right, drawHeight.toFloat(), color.toPaint())
|
||||
drawXOffset += width
|
||||
}
|
||||
mButtonRenderer.postInvalidate()
|
||||
}
|
||||
private fun makePaint(color: Int): Paint {
|
||||
return Paint().apply { this.color = color }
|
||||
private fun Long.toPaint(): Paint = Paint().apply { color = toInt() }
|
||||
|
||||
companion object {
|
||||
private const val TAG = "Brokenithm"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.github.brokenithm.activity
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.github.brokenithm.R
|
||||
import com.github.brokenithm.fragment.SettingsFragment
|
||||
|
||||
class SettingsActivity : FragmentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_settings)
|
||||
supportFragmentManager.beginTransaction().replace(R.id.settings_container, SettingsFragment()).commit()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.github.brokenithm.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.widget.EditText
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.github.brokenithm.R
|
||||
|
||||
class SettingsFragment : PreferenceFragmentCompat() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
preferenceManager.sharedPreferencesName = "settings"
|
||||
setPreferencesFromResource(R.xml.preferences, rootKey)
|
||||
val setDecimalEdit = { editText: EditText -> editText.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL }
|
||||
val entries = listOf("fat_touch_threshold", "extra_fat_touch_threshold", "gyro_air_lowest", "gyro_air_highest", "accel_air_threshold")
|
||||
for (entry in entries)
|
||||
findPreference<EditTextPreference>(entry)?.setOnBindEditTextListener(setDecimalEdit)
|
||||
}
|
||||
}
|
||||
+137
-137
@@ -1,138 +1,138 @@
|
||||
package com.github.brokenithm.util
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
@Suppress("unused")
|
||||
object AsyncTaskUtil {
|
||||
fun <R> CoroutineScope.executeAsyncTask(
|
||||
onPreExecute: () -> Unit = {},
|
||||
doInBackground: () -> R,
|
||||
onPostExecute: (R) -> Unit = {},
|
||||
onCancelled: () -> Unit = {}
|
||||
) = launch {
|
||||
try {
|
||||
onPreExecute() // runs in Main Thread
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
doInBackground() // runs in background thread without blocking the Main Thread
|
||||
}
|
||||
onPostExecute(result) // runs in Main Thread
|
||||
} catch (e: CancellationException) {
|
||||
onCancelled()
|
||||
}
|
||||
}
|
||||
|
||||
fun <P, R> CoroutineScope.executeAsyncTask(
|
||||
onPreExecute: () -> Unit = {},
|
||||
doInBackground: suspend (suspend (P) -> Unit) -> R,
|
||||
onPostExecute: (R) -> Unit = {},
|
||||
onProgressUpdate: (P) -> Unit,
|
||||
onCancelled: () -> Unit = {}
|
||||
) = launch {
|
||||
try {
|
||||
onPreExecute()
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
doInBackground {
|
||||
withContext(Dispatchers.Main) { onProgressUpdate(it) }
|
||||
}
|
||||
}
|
||||
onPostExecute(result)
|
||||
} catch (e: CancellationException) {
|
||||
onCancelled()
|
||||
}
|
||||
}
|
||||
|
||||
fun CoroutineScope.executeDelayedTask(
|
||||
task: () -> Unit,
|
||||
delayMillis: Long,
|
||||
onCancelled: () -> Unit = {}
|
||||
) = launch {
|
||||
try {
|
||||
delay(delayMillis)
|
||||
withContext(Dispatchers.Main) { task() }
|
||||
} catch (e: CancellationException) {
|
||||
onCancelled()
|
||||
}
|
||||
}
|
||||
|
||||
fun <A, P, R> CoroutineScope.executeAsyncTask(task: AsyncTask<A, P, R>, vararg arguments: A) = launch {
|
||||
try {
|
||||
task.onPreExecute()
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
task.doInBackground(*arguments)
|
||||
}
|
||||
task.onPostExecute(result)
|
||||
} catch (e: CancellationException) {
|
||||
task.onCancelled()
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AsyncTask<A, P, R> private constructor() {
|
||||
open fun onPreExecute() {}
|
||||
abstract suspend fun doInBackground(vararg argument: A): R
|
||||
open fun onPostExecute(result: R) {}
|
||||
open fun onCancelled() {}
|
||||
open fun onProgressUpdate(progress: P) {}
|
||||
protected fun publishProgress(progress: P) { onProgressUpdate(progress) }
|
||||
|
||||
private var mJob: Job? = null
|
||||
val isCompleted: Boolean
|
||||
get() = mJob?.isCompleted ?: false
|
||||
val isActive: Boolean
|
||||
get() = mJob?.isActive ?: false
|
||||
val isCancelled: Boolean
|
||||
get() = mJob?.isCancelled ?: false
|
||||
|
||||
fun execute(scope: CoroutineScope, vararg argument: A) {
|
||||
if (mJob != null && (!mJob!!.isCompleted || mJob!!.isActive)) {
|
||||
mJob!!.cancel()
|
||||
mJob = null
|
||||
}
|
||||
mJob = scope.executeAsyncTask(this, *argument)
|
||||
}
|
||||
|
||||
fun cancel(exception: CancellationException? = null) {
|
||||
if (mJob != null && !mJob!!.isCancelled) {
|
||||
mJob!!.cancel(exception)
|
||||
}
|
||||
}
|
||||
|
||||
fun cancel(message: String, cause: Throwable? = null) {
|
||||
if (mJob != null && !mJob!!.isCancelled) {
|
||||
mJob!!.cancel(message, cause)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun <A, R> make(
|
||||
onPreExecute: () -> Unit = {},
|
||||
doInBackground: (Array<out A>) -> R,
|
||||
onPostExecute: (R) -> Unit = {},
|
||||
onCancelled: () -> Unit = {}
|
||||
): AsyncTask<A, Unit, R> {
|
||||
return object : AsyncTask<A, Unit, R>() {
|
||||
override fun onPreExecute() { onPreExecute() }
|
||||
override suspend fun doInBackground(vararg argument: A): R { return doInBackground(argument) }
|
||||
override fun onPostExecute(result: R) { onPostExecute(result) }
|
||||
override fun onCancelled() { onCancelled() }
|
||||
}
|
||||
}
|
||||
|
||||
fun <A, P, R> make(
|
||||
onPreExecute: () -> Unit = {},
|
||||
doInBackground: (Array<out A>) -> R,
|
||||
onPostExecute: (R) -> Unit = {},
|
||||
onProgressUpdate: (P) -> Unit = {},
|
||||
onCancelled: () -> Unit = {}
|
||||
): AsyncTask<A, P, R> {
|
||||
return object : AsyncTask<A, P, R>() {
|
||||
override fun onPreExecute() { onPreExecute() }
|
||||
override suspend fun doInBackground(vararg argument: A): R { return doInBackground(argument) }
|
||||
override fun onPostExecute(result: R) { onPostExecute(result) }
|
||||
override fun onProgressUpdate(progress: P) { onProgressUpdate(progress) }
|
||||
override fun onCancelled() { onCancelled() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
package com.github.brokenithm.util
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
@Suppress("unused")
|
||||
object AsyncTaskUtil {
|
||||
fun <R> CoroutineScope.executeAsyncTask(
|
||||
onPreExecute: () -> Unit = {},
|
||||
doInBackground: () -> R,
|
||||
onPostExecute: (R) -> Unit = {},
|
||||
onCancelled: () -> Unit = {}
|
||||
) = launch {
|
||||
try {
|
||||
onPreExecute() // runs in Main Thread
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
doInBackground() // runs in background thread without blocking the Main Thread
|
||||
}
|
||||
onPostExecute(result) // runs in Main Thread
|
||||
} catch (e: CancellationException) {
|
||||
onCancelled()
|
||||
}
|
||||
}
|
||||
|
||||
fun <P, R> CoroutineScope.executeAsyncTask(
|
||||
onPreExecute: () -> Unit = {},
|
||||
doInBackground: suspend (suspend (P) -> Unit) -> R,
|
||||
onPostExecute: (R) -> Unit = {},
|
||||
onProgressUpdate: (P) -> Unit,
|
||||
onCancelled: () -> Unit = {}
|
||||
) = launch {
|
||||
try {
|
||||
onPreExecute()
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
doInBackground {
|
||||
withContext(Dispatchers.Main) { onProgressUpdate(it) }
|
||||
}
|
||||
}
|
||||
onPostExecute(result)
|
||||
} catch (e: CancellationException) {
|
||||
onCancelled()
|
||||
}
|
||||
}
|
||||
|
||||
fun CoroutineScope.executeDelayedTask(
|
||||
task: () -> Unit,
|
||||
delayMillis: Long,
|
||||
onCancelled: () -> Unit = {}
|
||||
) = launch {
|
||||
try {
|
||||
delay(delayMillis)
|
||||
withContext(Dispatchers.Main) { task() }
|
||||
} catch (e: CancellationException) {
|
||||
onCancelled()
|
||||
}
|
||||
}
|
||||
|
||||
fun <A, P, R> CoroutineScope.executeAsyncTask(task: AsyncTask<A, P, R>, vararg arguments: A) = launch {
|
||||
try {
|
||||
task.onPreExecute()
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
task.doInBackground(*arguments)
|
||||
}
|
||||
task.onPostExecute(result)
|
||||
} catch (e: CancellationException) {
|
||||
task.onCancelled()
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AsyncTask<A, P, R> private constructor() {
|
||||
open fun onPreExecute() {}
|
||||
abstract suspend fun doInBackground(vararg argument: A): R
|
||||
open fun onPostExecute(result: R) {}
|
||||
open fun onCancelled() {}
|
||||
open fun onProgressUpdate(progress: P) {}
|
||||
protected fun publishProgress(progress: P) { onProgressUpdate(progress) }
|
||||
|
||||
private var mJob: Job? = null
|
||||
val isCompleted: Boolean
|
||||
get() = mJob?.isCompleted ?: false
|
||||
val isActive: Boolean
|
||||
get() = mJob?.isActive ?: false
|
||||
val isCancelled: Boolean
|
||||
get() = mJob?.isCancelled ?: false
|
||||
|
||||
fun execute(scope: CoroutineScope, vararg argument: A) {
|
||||
if (mJob != null && (!mJob!!.isCompleted || mJob!!.isActive)) {
|
||||
mJob!!.cancel()
|
||||
mJob = null
|
||||
}
|
||||
mJob = scope.executeAsyncTask(this, *argument)
|
||||
}
|
||||
|
||||
fun cancel(exception: CancellationException? = null) {
|
||||
if (mJob != null && !mJob!!.isCancelled) {
|
||||
mJob!!.cancel(exception)
|
||||
}
|
||||
}
|
||||
|
||||
fun cancel(message: String, cause: Throwable? = null) {
|
||||
if (mJob != null && !mJob!!.isCancelled) {
|
||||
mJob!!.cancel(message, cause)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun <A, R> make(
|
||||
onPreExecute: () -> Unit = {},
|
||||
doInBackground: (Array<out A>) -> R,
|
||||
onPostExecute: (R) -> Unit = {},
|
||||
onCancelled: () -> Unit = {}
|
||||
): AsyncTask<A, Unit, R> {
|
||||
return object : AsyncTask<A, Unit, R>() {
|
||||
override fun onPreExecute() { onPreExecute() }
|
||||
override suspend fun doInBackground(vararg argument: A): R { return doInBackground(argument) }
|
||||
override fun onPostExecute(result: R) { onPostExecute(result) }
|
||||
override fun onCancelled() { onCancelled() }
|
||||
}
|
||||
}
|
||||
|
||||
fun <A, P, R> make(
|
||||
onPreExecute: () -> Unit = {},
|
||||
doInBackground: (Array<out A>) -> R,
|
||||
onPostExecute: (R) -> Unit = {},
|
||||
onProgressUpdate: (P) -> Unit = {},
|
||||
onCancelled: () -> Unit = {}
|
||||
): AsyncTask<A, P, R> {
|
||||
return object : AsyncTask<A, P, R>() {
|
||||
override fun onPreExecute() { onPreExecute() }
|
||||
override suspend fun doInBackground(vararg argument: A): R { return doInBackground(argument) }
|
||||
override fun onPostExecute(result: R) { onPostExecute(result) }
|
||||
override fun onProgressUpdate(progress: P) { onProgressUpdate(progress) }
|
||||
override fun onCancelled() { onCancelled() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.github.brokenithm.util
|
||||
|
||||
import android.nfc.Tag
|
||||
import android.nfc.tech.NfcF
|
||||
import android.nfc.tech.TagTechnology
|
||||
|
||||
@Suppress("Unused", "MemberVisibilityCanBePrivate", "SpellCheckingInspection", "PropertyName")
|
||||
class FeliCa private constructor(private val nfcF: NfcF) : TagTechnology {
|
||||
private lateinit var mTag: Tag
|
||||
|
||||
var IDm: ByteArray? = null
|
||||
private set
|
||||
|
||||
var PMm: ByteArray? = null
|
||||
private set
|
||||
|
||||
val systemCode: ByteArray
|
||||
get() = nfcF.systemCode
|
||||
|
||||
override fun connect() = nfcF.connect()
|
||||
override fun isConnected() = nfcF.isConnected
|
||||
override fun close() {
|
||||
IDm = null
|
||||
PMm = null
|
||||
nfcF.close()
|
||||
}
|
||||
override fun getTag() = mTag
|
||||
fun getMaxTransceiveLength() = nfcF.maxTransceiveLength
|
||||
fun transceive(data: ByteArray): ByteArray = nfcF.transceive(data)
|
||||
var timeout: Int
|
||||
get() = nfcF.timeout
|
||||
set(value) { nfcF.timeout = value }
|
||||
|
||||
private fun checkConnected() {
|
||||
if (!nfcF.isConnected)
|
||||
throw IllegalStateException("Call connect() first!")
|
||||
}
|
||||
|
||||
fun poll(systemCode: Int = 0xFFFF, requestCode: Int = 0x01) {
|
||||
checkConnected()
|
||||
|
||||
val buffer = ByteArray(6)
|
||||
buffer[0] = 6
|
||||
buffer[1] = FELICA_CMD_POLLING
|
||||
buffer[2] = ((systemCode shr 8) and 0xff).toByte()
|
||||
buffer[3] = (systemCode and 0xff).toByte()
|
||||
buffer[4] = requestCode.toByte()
|
||||
buffer[5] = 0
|
||||
val result = nfcF.transceive(buffer)
|
||||
if (result.size != 18 && result.size != 20)
|
||||
throw IllegalStateException("Poll FeliCa response incorrect")
|
||||
IDm = result.copyOfRange(2, 10)
|
||||
PMm = result.copyOfRange(10, 18)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val FELICA_CMD_POLLING: Byte = 0x00
|
||||
fun get(tag: Tag): FeliCa? {
|
||||
val realTag = NfcF.get(tag) ?: return null
|
||||
return FeliCa(realTag).apply { mTag = tag }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,16 +110,21 @@
|
||||
app:layout_constraintStart_toEndOf="@+id/button_coin"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/check_enable_air"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
<TextView
|
||||
android:id="@+id/text_switch_air"
|
||||
android:layout_width="90dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Enable Air"
|
||||
android:background="#444444"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:padding="8dp"
|
||||
android:text="@string/disable_air"
|
||||
android:textColor="@color/white"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/button_card"
|
||||
app:layout_constraintStart_toEndOf="@+id/button_card"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="@+id/button_card" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="195dp"
|
||||
@@ -198,25 +203,15 @@
|
||||
app:layout_constraintStart_toEndOf="@+id/button_service"
|
||||
app:layout_constraintTop_toTopOf="@+id/button_service" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/check_vibrate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:text="@string/enable_vibration"
|
||||
android:textColor="@color/white"
|
||||
app:layout_constraintStart_toEndOf="@+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" />
|
||||
app:layout_constraintBottom_toBottomOf="@+id/check_show_delay"
|
||||
app:layout_constraintStart_toEndOf="@+id/check_show_delay"
|
||||
app:layout_constraintTop_toTopOf="@+id/check_show_delay" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_mode"
|
||||
@@ -228,7 +223,7 @@
|
||||
android:focusable="true"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:padding="8dp"
|
||||
android:text="UDP"
|
||||
android:text="@string/udp"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
@@ -236,6 +231,16 @@
|
||||
app:layout_constraintStart_toEndOf="@+id/check_simple_air"
|
||||
app:layout_constraintTop_toTopOf="@+id/check_simple_air" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_settings"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:text="@string/settings"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/text_mode"
|
||||
app:layout_constraintStart_toEndOf="@+id/text_mode"
|
||||
app:layout_constraintTop_toTopOf="@+id/text_mode" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</net.cachapa.expandablelayout.ExpandableLayout>
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/layout_top"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#202020"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_title2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="Brokenithm-Evolved Settings"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/settings_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/layout_top">
|
||||
|
||||
</FrameLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -12,7 +12,7 @@
|
||||
<string name="test">Test</string>
|
||||
<string name="service">Service</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 size:%f\n%s</string>
|
||||
<string name="current_latency">Current Latency: %f ms</string>
|
||||
<string name="press_again_to_exit">Press again to exit</string>
|
||||
<string name="show_delay">Show Delay</string>
|
||||
@@ -21,4 +21,20 @@
|
||||
|
||||
<string name="tcp">TCP</string>
|
||||
<string name="udp">UDP</string>
|
||||
|
||||
<string name="disable_air">Disable Air</string>
|
||||
<string name="gyro_air">Gyro Air</string>
|
||||
<string name="accel_air">Accel Air</string>
|
||||
<string name="touch_air">Touch Air</string>
|
||||
<string name="enable_air">Enable Air</string>
|
||||
|
||||
<string name="settings">Settings</string>
|
||||
<string name="gyro_air_highest">Gyro Air Highest Bound</string>
|
||||
<string name="accel_air_threshold">Accel Air Threshold</string>
|
||||
<string name="gyro_air_lowest">Gyro Air Lowest Bound</string>
|
||||
<string name="extra_fat_touch_threshold">Extra Fat Touch Threshold</string>
|
||||
<string name="fat_touch_threshold">Fat Touch Threshold</string>
|
||||
<string name="enable_touch_size">Enable Touch Size Check</string>
|
||||
<string name="enable_nfc">Enable NFC</string>
|
||||
<string name="enable_vibrate">Enable Vibration</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<tech-list>
|
||||
<tech>android.nfc.tech.NfcA</tech>
|
||||
<tech>android.nfc.tech.NfcF</tech>
|
||||
<tech>android.nfc.tech.NdefFormatable</tech>
|
||||
<tech>android.nfc.tech.MifareClassic</tech>
|
||||
</tech-list>
|
||||
</resources>
|
||||
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<PreferenceCategory android:title="Common">
|
||||
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="true"
|
||||
android:key="enable_vibrate"
|
||||
android:title="@string/enable_vibrate" />
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="true"
|
||||
android:key="enable_nfc"
|
||||
android:title="@string/enable_nfc" />
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory android:title="Touch Judge">
|
||||
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:key="enable_touch_size"
|
||||
android:title="@string/enable_touch_size" />
|
||||
<EditTextPreference
|
||||
android:defaultValue="0.027"
|
||||
android:key="fat_touch_threshold"
|
||||
android:selectAllOnFocus="true"
|
||||
android:singleLine="true"
|
||||
android:title="@string/fat_touch_threshold" />
|
||||
<EditTextPreference
|
||||
android:defaultValue="0.035"
|
||||
android:key="extra_fat_touch_threshold"
|
||||
android:selectAllOnFocus="true"
|
||||
android:singleLine="true"
|
||||
android:title="@string/extra_fat_touch_threshold" />
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory android:title="Sensor">
|
||||
|
||||
<EditTextPreference
|
||||
android:defaultValue="0.8"
|
||||
android:key="gyro_air_lowest"
|
||||
android:selectAllOnFocus="true"
|
||||
android:singleLine="true"
|
||||
android:title="@string/gyro_air_lowest" />
|
||||
<EditTextPreference
|
||||
android:defaultValue="1.35"
|
||||
android:key="gyro_air_highest"
|
||||
android:selectAllOnFocus="true"
|
||||
android:singleLine="true"
|
||||
android:title="@string/gyro_air_highest" />
|
||||
<EditTextPreference
|
||||
android:defaultValue="2.0"
|
||||
android:key="accel_air_threshold"
|
||||
android:selectAllOnFocus="true"
|
||||
android:singleLine="true"
|
||||
android:title="@string/accel_air_threshold" />
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
ext.kotlin_version = "1.4.30"
|
||||
ext.kotlin_version = "1.5.21"
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:4.1.2"
|
||||
classpath 'com.android.tools.build:gradle:4.2.2'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
||||
+1
-1
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
|
||||
|
||||
Reference in New Issue
Block a user