mirror of
https://github.com/nacabaro/vbhelper.git
synced 2026-07-30 00:31:54 +00:00
Merge pull request #55 from b13st/fix/vitalwear-hce-transfer
Fix VitalWear HCE transfer in both directions
This commit is contained in:
commit
81bfec5ac5
@ -49,7 +49,8 @@ class MainActivity : ComponentActivity() {
|
||||
application.container.dataStoreSecretsRepository.secretsFlow,
|
||||
this,
|
||||
this::registerActivityLifecycleListener,
|
||||
this::unregisterActivityLifecycleListener
|
||||
this::unregisterActivityLifecycleListener,
|
||||
application.container.db,
|
||||
)
|
||||
val settingsScreenController = SettingsScreenControllerImpl(this)
|
||||
val itemsScreenController = ItemsScreenControllerImpl(this)
|
||||
|
||||
@ -3,6 +3,7 @@ package com.github.nacabaro.vbhelper.screens.scanScreen
|
||||
import android.content.Intent
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.Tag
|
||||
import android.nfc.tech.IsoDep
|
||||
import android.nfc.tech.NfcA
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
@ -15,12 +16,16 @@ import com.github.cfogrady.vbnfc.be.BENfcCharacter
|
||||
import com.github.cfogrady.vbnfc.data.NfcCharacter
|
||||
import com.github.cfogrady.vbnfc.vb.VBNfcCharacter
|
||||
import com.github.nacabaro.vbhelper.ActivityLifecycleListener
|
||||
import com.github.nacabaro.vbhelper.database.AppDatabase
|
||||
import com.github.nacabaro.vbhelper.domain.card.Card
|
||||
import com.github.nacabaro.vbhelper.screens.scanScreen.converters.FromNfcConverter
|
||||
import com.github.nacabaro.vbhelper.screens.scanScreen.converters.ToNfcConverter
|
||||
import com.github.nacabaro.vbhelper.source.VitalWearCharacterExporter
|
||||
import com.github.nacabaro.vbhelper.source.VitalWearCharacterImporter
|
||||
import com.github.nacabaro.vbhelper.source.getCryptographicTransformerMap
|
||||
import com.github.nacabaro.vbhelper.source.isMissingSecrets
|
||||
import com.github.nacabaro.vbhelper.source.proto.Secrets
|
||||
import com.github.nacabaro.vbhelper.transfer.hce.VitalWearHceReaderClient
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
@ -32,8 +37,10 @@ class ScanScreenControllerImpl(
|
||||
private val componentActivity: ComponentActivity,
|
||||
private val registerActivityLifecycleListener: (String, ActivityLifecycleListener)->Unit,
|
||||
private val unregisterActivityLifecycleListener: (String)->Unit,
|
||||
private val database: AppDatabase,
|
||||
): ScanScreenController {
|
||||
private var lastScannedCharacter: NfcCharacter? = null
|
||||
private var pendingExportCharacterId: Long? = null
|
||||
private val nfcAdapter: NfcAdapter
|
||||
|
||||
init {
|
||||
@ -46,7 +53,9 @@ class ScanScreenControllerImpl(
|
||||
}
|
||||
|
||||
override fun onClickRead(secrets: Secrets, onComplete: ()->Unit, onMultipleCards: (List<Card>) -> Unit) {
|
||||
handleTag(secrets) { tagCommunicator ->
|
||||
handleTag(
|
||||
secrets,
|
||||
handlerFunc = { tagCommunicator ->
|
||||
try {
|
||||
val character = tagCommunicator.receiveCharacter()
|
||||
val resultMessage = characterFromNfc(character) { cards, nfcCharacter ->
|
||||
@ -63,7 +72,27 @@ class ScanScreenControllerImpl(
|
||||
onComplete.invoke()
|
||||
componentActivity.getString(R.string.scan_error_generic)
|
||||
}
|
||||
},
|
||||
hceHandler = { isoDep ->
|
||||
try {
|
||||
val client = VitalWearHceReaderClient(isoDep)
|
||||
val character = client.receiveCharacterFromWatch()
|
||||
val importer = VitalWearCharacterImporter(database)
|
||||
val result = importer.importCharacter(character)
|
||||
Log.i("NFC_READ", "VitalWear HCE import: ${result.message}")
|
||||
componentActivity.runOnUiThread {
|
||||
Toast.makeText(componentActivity, result.message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("NFC_READ", "Error reading character from VitalWear HCE", e)
|
||||
componentActivity.runOnUiThread {
|
||||
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_error_generic) + ": " + (e.message ?: e.javaClass.simpleName), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
} finally {
|
||||
onComplete()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun cancelRead() {
|
||||
@ -83,29 +112,45 @@ class ScanScreenControllerImpl(
|
||||
unregisterActivityLifecycleListener.invoke(key)
|
||||
}
|
||||
|
||||
// EXTRACTED DIRECTLY FROM EXAMPLE APP
|
||||
private fun handleTag(secrets: Secrets, handlerFunc: (TagCommunicator)->String) {
|
||||
private fun handleTag(
|
||||
secrets: Secrets,
|
||||
handlerFunc: (TagCommunicator) -> String,
|
||||
hceHandler: ((IsoDep) -> Unit)? = null,
|
||||
) {
|
||||
if (!nfcAdapter.isEnabled) {
|
||||
showWirelessSettings()
|
||||
} else {
|
||||
val options = Bundle()
|
||||
// Work around for some broken Nfc firmware implementations that poll the card too fast
|
||||
options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 250)
|
||||
nfcAdapter.enableReaderMode(componentActivity, buildOnReadTag(secrets, handlerFunc), NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK,
|
||||
nfcAdapter.enableReaderMode(
|
||||
componentActivity,
|
||||
buildOnReadTag(secrets, handlerFunc, hceHandler),
|
||||
NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK,
|
||||
options
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// EXTRACTED DIRECTLY FROM EXAMPLE APP
|
||||
private fun buildOnReadTag(secrets: Secrets, handlerFunc: (TagCommunicator)->String): (Tag)->Unit {
|
||||
return { tag->
|
||||
private fun buildOnReadTag(
|
||||
secrets: Secrets,
|
||||
handlerFunc: (TagCommunicator) -> String,
|
||||
hceHandler: ((IsoDep) -> Unit)? = null,
|
||||
): (Tag) -> Unit {
|
||||
return { tag ->
|
||||
val isoDep = IsoDep.get(tag)
|
||||
if (isoDep != null && hceHandler != null) {
|
||||
// VitalWear watch via HCE / ISO-DEP
|
||||
isoDep.connect()
|
||||
isoDep.use { hceHandler(isoDep) }
|
||||
} else {
|
||||
// Physical Vital Bracelet via raw NFC-A
|
||||
val nfcData = NfcA.get(tag)
|
||||
if (nfcData == null) {
|
||||
componentActivity.runOnUiThread {
|
||||
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_tag_not_vb), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nfcData.connect()
|
||||
nfcData.use {
|
||||
val tagCommunicator = TagCommunicator.getInstance(nfcData, secrets.getCryptographicTransformerMap())
|
||||
@ -116,6 +161,8 @@ class ScanScreenControllerImpl(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkSecrets() {
|
||||
componentActivity.lifecycleScope.launch(Dispatchers.IO) {
|
||||
@ -132,16 +179,16 @@ class ScanScreenControllerImpl(
|
||||
nfcCharacter: NfcCharacter,
|
||||
onComplete: () -> Unit
|
||||
) {
|
||||
handleTag(secrets) { tagCommunicator ->
|
||||
handleTag(
|
||||
secrets,
|
||||
handlerFunc = { tagCommunicator ->
|
||||
try {
|
||||
if (nfcCharacter is VBNfcCharacter) {
|
||||
Log.d("SendCharacter", "VBNfcCharacter")
|
||||
val castNfcCharacter: VBNfcCharacter = nfcCharacter
|
||||
tagCommunicator.sendCharacter(castNfcCharacter)
|
||||
tagCommunicator.sendCharacter(nfcCharacter)
|
||||
} else if (nfcCharacter is BENfcCharacter) {
|
||||
Log.d("SendCharacter", "BENfcCharacter")
|
||||
val castNfcCharacter: BENfcCharacter = nfcCharacter
|
||||
tagCommunicator.sendCharacter(castNfcCharacter)
|
||||
tagCommunicator.sendCharacter(nfcCharacter)
|
||||
}
|
||||
onComplete.invoke()
|
||||
componentActivity.getString(R.string.scan_sent_character_success)
|
||||
@ -149,7 +196,12 @@ class ScanScreenControllerImpl(
|
||||
Log.e("TAG", e.stackTraceToString())
|
||||
componentActivity.getString(R.string.scan_error_generic)
|
||||
}
|
||||
},
|
||||
hceHandler = { _ ->
|
||||
// Character was already sent in onClickCheckCard's HCE handler.
|
||||
onComplete()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onClickCheckCard(
|
||||
@ -157,11 +209,42 @@ class ScanScreenControllerImpl(
|
||||
nfcCharacter: NfcCharacter,
|
||||
onComplete: () -> Unit
|
||||
) {
|
||||
handleTag(secrets) { tagCommunicator ->
|
||||
handleTag(
|
||||
secrets,
|
||||
handlerFunc = { tagCommunicator ->
|
||||
tagCommunicator.prepareDIMForCharacter(nfcCharacter.dimId)
|
||||
onComplete.invoke()
|
||||
componentActivity.getString(R.string.scan_sent_dim_success)
|
||||
},
|
||||
hceHandler = { isoDep ->
|
||||
// VitalWear watch: send the character proto directly via HCE.
|
||||
val characterId = pendingExportCharacterId
|
||||
if (characterId == null) {
|
||||
Log.e("NFC_WRITE", "No pending character to send to VitalWear")
|
||||
componentActivity.runOnUiThread {
|
||||
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_error_generic), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
onComplete()
|
||||
return@handleTag
|
||||
}
|
||||
try {
|
||||
val proto = VitalWearCharacterExporter(componentActivity, database).buildCharacterProto(characterId)
|
||||
val client = VitalWearHceReaderClient(isoDep)
|
||||
client.sendCharacterToWatch(proto)
|
||||
Log.i("NFC_WRITE", "Character sent to VitalWear via HCE")
|
||||
componentActivity.runOnUiThread {
|
||||
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_sent_character_success), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("NFC_WRITE", "Error sending character to VitalWear HCE", e)
|
||||
componentActivity.runOnUiThread {
|
||||
Toast.makeText(componentActivity, componentActivity.getString(R.string.scan_error_generic) + ": " + (e.message ?: e.javaClass.simpleName), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
} finally {
|
||||
onComplete()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// EXTRACTED DIRECTLY FROM EXAMPLE APP
|
||||
@ -181,10 +264,8 @@ class ScanScreenControllerImpl(
|
||||
}
|
||||
|
||||
override suspend fun characterToNfc(characterId: Long): NfcCharacter {
|
||||
val nfcGenerator = ToNfcConverter(
|
||||
componentActivity = componentActivity
|
||||
)
|
||||
|
||||
pendingExportCharacterId = characterId
|
||||
val nfcGenerator = ToNfcConverter(componentActivity = componentActivity)
|
||||
val character = nfcGenerator.characterToNfc(characterId)
|
||||
Log.d("CharacterType", character.toString())
|
||||
return character
|
||||
|
||||
@ -15,14 +15,13 @@ class VitalWearCharacterExporter(
|
||||
private val context: Context,
|
||||
private val database: AppDatabase
|
||||
) {
|
||||
fun buildShareIntent(characterId: Long): Intent {
|
||||
return runBlocking {
|
||||
fun buildCharacterProto(characterId: Long): Character = runBlocking {
|
||||
val characterWithSprites = database.userCharacterDao().getCharacterWithSprites(characterId)
|
||||
val userCharacter = database.userCharacterDao().getCharacter(characterId)
|
||||
val card = database.cardDao().getCardByCharacterIdSync(characterId)
|
||||
?: error("Card not found for character $characterId")
|
||||
val cardProgress = database.cardProgressDao().getCardProgressSync(card.id) ?: 0
|
||||
val proto = Character.newBuilder()
|
||||
Character.newBuilder()
|
||||
.setCardId(card.cardId)
|
||||
.setCardName(card.name)
|
||||
.setCharacterStats(
|
||||
@ -61,6 +60,11 @@ class VitalWearCharacterExporter(
|
||||
}
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
fun buildShareIntent(characterId: Long): Intent {
|
||||
return runBlocking {
|
||||
val proto = buildCharacterProto(characterId)
|
||||
|
||||
val exportDir = File(context.cacheDir, "exports").apply { mkdirs() }
|
||||
val exportFile = File(exportDir, "vbhelper_character_$characterId.vitalwear")
|
||||
|
||||
@ -17,6 +17,7 @@ dependencyResolutionManagement {
|
||||
mavenLocal()
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url = uri("https://jitpack.io") }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user