Skip to content

Instantly share code, notes, and snippets.

@vitorxsilva
Created January 18, 2024 18:44
Show Gist options
  • Save vitorxsilva/ce74101755f4b3acc77c2dc988b4d60f to your computer and use it in GitHub Desktop.
Save vitorxsilva/ce74101755f4b3acc77c2dc988b4d60f to your computer and use it in GitHub Desktop.
class MainActivity : ComponentActivity() {
private val receiver = MifareCallbackReceiver(::onBroadcast)
private var callback = ""
private val data = mutableStateListOf<String>()
private val inputValue = mutableStateOf(TextFieldValue())
private val inputSector = mutableStateOf(TextFieldValue("1"))
private val inputBlock = mutableStateOf(TextFieldValue("0"))
private val cardData = mutableStateOf("")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setupBroadcastReceiver()
setContent {
LioMifareServiceAndroidTheme {
// A surface container using the 'background' color from the theme
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
Column(Modifier.fillMaxSize()) {
Row(Modifier.fillMaxWidth()) {
Button(::startReadFlow) {
Text("Read")
}
Spacer(Modifier.width(Dp(4f)))
Button(::deactivate) {
Text("Deactivate")
}
Spacer(Modifier.width(Dp(4f)))
Button(::clearLogs) {
Text("Clear Logs")
}
}
Row(Modifier.fillMaxWidth()) {
Column(modifier = Modifier.weight(1f)) {
NumericInput("Setor", inputSector)
}
Spacer(modifier = Modifier.width(Dp(1f)))
Column(modifier = Modifier.weight(1f)) {
NumericInput("Bloco", inputBlock)
}
Spacer(modifier = Modifier.width(Dp(1f)))
Column(modifier = Modifier.weight(1f)) {
NumericInput("Valor", inputValue)
}
}
Row {
Spacer(Modifier.width(Dp(2f)))
Button(::startWriteFlow) {
Text("Write")
}
Spacer(Modifier.width(Dp(2f)))
Button(::startIncrementFlow) {
Text("Inc")
}
Spacer(Modifier.width(Dp(2f)))
Button(::startDecrementFlow) {
Text("Dec")
}
}
Row {
Text("Last Read")
}
Row {
Text(cardData.value)
}
Spacer(Modifier.height(Dp(16f)))
Row {
Text("Logs")
}
LazyColumn(Modifier.fillMaxSize()) {
items(data) {
Row(Modifier.fillMaxWidth()) {
Text(it)
}
}
}
}
}
}
}
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(receiver)
}
private fun clearLogs() {
data.clear()
cardData.value = ""
}
private fun startReadFlow() {
callback = "AUTH_CALLBACK_READ"
detectCard()
}
private fun startWriteFlow() {
callback = "AUTH_CALLBACK_WRITE"
detectCard()
}
private fun startIncrementFlow() {
callback = "AUTH_CALLBACK_INCR"
detectCard()
}
private fun startDecrementFlow() {
callback = "AUTH_CALLBACK_DECR"
detectCard()
}
private fun onBroadcast(intent: Intent) {
log("Broadcast received")
log("> action: ${intent.action}")
log("> result: ${intent.getStringExtra("result")}")
log("> detail: ${intent.getStringExtra("detail")}")
intent.extras?.getByteArray("data")?.let {
cardData.value = "${it.toInt()} (${it.toHex()})"
log("> data: ${it.toInt()} (${it.toHex()})")
log("> data length: ${it.size}")
}
when (intent.action) {
"DETECT_CALLBACK" -> authenticate()
"AUTH_CALLBACK_READ" -> readCardData()
"AUTH_CALLBACK_WRITE" -> writeData()
"AUTH_CALLBACK_INCR" -> increment()
"AUTH_CALLBACK_DECR" -> decrement()
"READ_CALLBACK" -> log("Card data read finished")
"WRITE_CALLBACK" -> log("Card data write finished")
"DEACTIVATE_CALLBACK" -> log("All Done")
}
}
private fun setupBroadcastReceiver() {
IntentFilter().let {
listOf(
"$PACKAGE.DETECTING",
"$PACKAGE.DETECTED",
"DETECT_CALLBACK",
"AUTH_CALLBACK_READ",
"AUTH_CALLBACK_WRITE",
"AUTH_CALLBACK_INCR",
"AUTH_CALLBACK_DECR",
"READ_CALLBACK",
"WRITE_CALLBACK",
"INCREMENT_CALLBACK",
"DECREMENT_CALLBACK",
"DEACTIVATE_CALLBACK",
).forEach(it::addAction)
ContextCompat.registerReceiver(this, receiver, it, ContextCompat.RECEIVER_EXPORTED)
}
}
private fun detectCard() {
log("Calling service -> DETECT")
callService("$PACKAGE.DETECT", "DETECT_CALLBACK")
}
private fun authenticate() {
log("Calling service -> AUTHENTICATE")
callService("$PACKAGE.AUTHENTICATE", callback) {
putExtra("sector", inputSector.value.toByte())
putExtra("key", MifareClassic.KEY_DEFAULT)
putExtra("keyType", 'A')
}
}
private fun readCardData() {
log("Calling service -> READ")
callService("$PACKAGE.READ", "READ_CALLBACK") {
putExtra("sector", inputSector.value.toByte())
putExtra("block", inputBlock.value.toByte())
}
}
private fun writeData() {
log("Calling service -> WRITE")
callService("$PACKAGE.WRITE", "WRITE_CALLBACK") {
putExtra("sector", inputSector.value.toByte())
putExtra("block", inputBlock.value.toByte())
putExtra("data", inputValue.value.text.toInt().toByteArray())
}
}
private fun increment() {
log("Calling service -> INCREMENT")
callService("$PACKAGE.INCREMENT", "INCREMENT_CALLBACK") {
putExtra("sector", inputSector.value.toByte())
putExtra("block", inputBlock.value.toByte())
putExtra("value", inputValue.value.text.toInt().toByte())
}
}
private fun decrement() {
log("Calling service -> DECREMENT")
callService("$PACKAGE.DECREMENT", "DECREMENT_CALLBACK") {
putExtra("sector", inputSector.value.toByte())
putExtra("block", inputBlock.value.toByte())
putExtra("value", inputValue.value.text.toInt().toByte())
}
}
private fun deactivate() {
log("Calling service -> DEACTIVATE")
callService("$PACKAGE.DEACTIVATE", "DEACTIVATE_CALLBACK")
}
private fun callService(action: String, cbAction: String, setup: Intent.() -> Unit = {}) {
Intent(action)
.setPackage("cielo.lio.cashless")
.putExtra("cbType", 'B')
.putExtra("cbPackage", packageName)
.putExtra("cbAction", cbAction)
.also(setup)
.let(::startService)
}
private fun log(text: String) {
Log.i(TAG, text)
data.add(text)
}
}
class MifareCallbackReceiver(private val onBroadcast: (Intent) -> Unit) : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
intent?.let(onBroadcast)
}
}
@Composable
fun NumericInput(label: String, state: MutableState<TextFieldValue>) {
TextField(
label = { Text(label) },
value = state.value,
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
onValueChange = {
state.value = it
})
}
fun TextFieldValue.toByte() = this.text.trim().toInt().toByte()
fun ByteArray.toHex() = joinToString("") { "%02x".format(it) }
fun ByteArray.toInt() = BigInteger(this).toInt()
fun Int.toByteArray() = "%02x".format(this).hexToByteArray()
fun String.hexToByteArray(): ByteArray = padStart(32, '0').chunked(2).map { it.toInt(16).toByte() }.toByteArray()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment