From aa1f4152b5cfe9e6ef8cdbfa279d70a6ab95656d Mon Sep 17 00:00:00 2001 From: CarbideCowboy Date: Fri, 4 Oct 2024 15:36:35 -0500 Subject: [PATCH] Refined NfcAdapterController to inject NfcControllerFactory internally. This means that NfcViewModel no longer needs the factory class passed in. --- app/build.gradle.kts | 9 +-- app/src/main/AndroidManifest.xml | 2 + .../presentation/IntraExampleActivity.kt | 57 ++++++++++++++++--- .../presentation/IntraExampleViewModel.kt | 45 +++++++++++++++ build.gradle.kts | 4 +- intra/build.gradle.kts | 4 +- .../hoker/intra/data/IsodepControllerImpl.kt | 16 +++--- .../hoker/intra/data/NfcAControllerImpl.kt | 4 ++ .../hoker/intra/data/NfcVControllerImpl.kt | 10 ++-- .../main/java/com/hoker/intra/di/NfcModule.kt | 7 ++- .../com/hoker/intra/domain/NfcActivity.kt | 2 + .../intra/domain/NfcAdapterController.kt | 28 +++++++-- .../com/hoker/intra/domain/NfcController.kt | 1 + .../com/hoker/intra/domain/NfcViewModel.kt | 11 +--- 14 files changed, 154 insertions(+), 46 deletions(-) create mode 100644 app/src/main/java/com/hoker/intra_example/presentation/IntraExampleViewModel.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 78fc894..b281974 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -7,14 +7,14 @@ plugins { android { namespace = "com.carbidecowboy.intra_example" - compileSdk = 34 + compileSdk = 35 defaultConfig { applicationId = "com.hoker.intra_example" minSdk = 28 targetSdk = 35 - versionCode = 131 - versionName = "1.3.1" + versionCode = 133 + versionName = "1.3.3" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { @@ -57,6 +57,7 @@ dependencies { implementation("androidx.appcompat:appcompat:1.6.1") implementation("com.google.android.material:material:1.12.0") + implementation("androidx.hilt:hilt-navigation-compose:1.2.0") implementation("androidx.navigation:navigation-compose:2.7.7") implementation("androidx.compose.ui:ui:1.6.7") implementation("androidx.compose.material:material:1.6.7") @@ -68,5 +69,5 @@ dependencies { implementation("androidx.constraintlayout:constraintlayout-compose-android:1.1.0-alpha13") implementation(project(":intra")) - implementation("com.github.CarbideCowboy:Supra:0.0.6") + implementation("com.github.h0ker:Supra:0.0.9") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2138eef..1053f24 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,8 @@ + + + Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show() + } + } + SupraGyroScaffold( - borderColor = Color.Black, - backgroundColor = Color.DarkGray + borderColor = Color.DarkGray, + backgroundColor = Color.Black ) { - + Column( + modifier = Modifier.padding(16.dp) + ) { + if (viewModel.jwtText == null) { + Text( + text = "Scan to get JWT", + color = Color.White, + fontSize = 24.sp, + fontWeight = FontWeight.Bold + ) + } + viewModel.jwtText?.let { jwt -> + Text( + text = "JWT:", + color = Color.White, + fontSize = 24.sp, + fontWeight = FontWeight.Bold + ) + Text( + text = jwt, + color = Color.White, + fontSize = 16.sp + ) + } + } } } } diff --git a/app/src/main/java/com/hoker/intra_example/presentation/IntraExampleViewModel.kt b/app/src/main/java/com/hoker/intra_example/presentation/IntraExampleViewModel.kt new file mode 100644 index 0000000..f82972c --- /dev/null +++ b/app/src/main/java/com/hoker/intra_example/presentation/IntraExampleViewModel.kt @@ -0,0 +1,45 @@ +package com.hoker.intra_example.presentation + +import android.nfc.Tag +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.viewModelScope +import com.hoker.intra.domain.NfcAdapterController +import com.hoker.intra.domain.NfcController +import com.hoker.intra.domain.NfcViewModel +import com.hoker.intra.domain.OperationResult +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class IntraExampleViewModel @Inject constructor( + nfcAdapterController: NfcAdapterController +): NfcViewModel(nfcAdapterController) { + + private val _jwtText: MutableState = mutableStateOf(null) + val jwtText: String? + get() { return _jwtText.value } + + private val _errorChannel = Channel(Channel.BUFFERED) + var errorFlow = _errorChannel.receiveAsFlow() + + override fun onNfcTagDiscovered(tag: Tag, nfcController: NfcController) { + viewModelScope.launch(Dispatchers.IO) { + nfcController.withConnection(tag) { + when (val result = nfcController.getVivokeyJwt(tag)) { + is OperationResult.Success -> { + _jwtText.value = result.data + } + is OperationResult.Failure -> { + _jwtText.value = null + _errorChannel.trySend(result.exception?.message ?: "Error getting JWT") + } + } + } + } + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 2c1775e..aeb7f98 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,8 +7,8 @@ buildscript { } plugins { - id("com.android.application") version "8.4.0" apply false + id("com.android.application") version "8.6.0" apply false id("org.jetbrains.kotlin.android") version "1.9.24" apply false - id("com.android.library") version "8.4.0" apply false + id("com.android.library") version "8.6.0" apply false id("com.google.devtools.ksp") version "1.9.10-1.0.13" apply false } \ No newline at end of file diff --git a/intra/build.gradle.kts b/intra/build.gradle.kts index 4243c1e..0acf889 100644 --- a/intra/build.gradle.kts +++ b/intra/build.gradle.kts @@ -59,9 +59,9 @@ afterEvaluate { publications { create("release") { from(components["release"]) - groupId = "com.github.CarbideCowboy" + groupId = "com.github.h0ker" artifactId = "Intra" - version = "1.3.1" + version = "1.3.3" } } } diff --git a/intra/src/main/java/com/hoker/intra/data/IsodepControllerImpl.kt b/intra/src/main/java/com/hoker/intra/data/IsodepControllerImpl.kt index ff748d6..87fda41 100644 --- a/intra/src/main/java/com/hoker/intra/data/IsodepControllerImpl.kt +++ b/intra/src/main/java/com/hoker/intra/data/IsodepControllerImpl.kt @@ -139,17 +139,14 @@ class IsodepControllerImpl @Inject constructor( command[6] = 0x00.toByte() command[7] = 0x00.toByte() - val isoDep = IsoDep.get(tag) - isoDep.connect() - //TODO: Add error check on this result - isoDep.transceive(NDEF_SEL) + isoDep?.transceive(NDEF_SEL) // send part 1 command - val part1Result = isoDep.transceive(command) + val part1Result = isoDep?.transceive(command) var piccChallenge = part1Result - piccChallenge = piccChallenge.copyOfRange(0, 16) + piccChallenge = piccChallenge?.copyOfRange(0, 16) println("PICC Challenge:\n${Hex.encodeHexString(piccChallenge)}\n\n") val challengeRequest = ChallengeRequest( @@ -179,8 +176,7 @@ class IsodepControllerImpl @Inject constructor( command[37] = 0x00.toByte() Log.i("Part 2 Command", Hex.encodeHexString(command)) - val response = isoDep.transceive(command) - isoDep.close() + val response = isoDep?.transceive(command) Log.i("Response", Hex.encodeHexString(response)) val responseString = Hex.encodeHexString(response) @@ -267,6 +263,10 @@ class IsodepControllerImpl @Inject constructor( ) } + override fun getMaxTransceiveLength(): Int? { + return isoDep?.maxTransceiveLength + } + override suspend fun writeNdefMessage(tag: Tag, message: NdefMessage): OperationResult { return try { val ndef = Ndef.get(tag) diff --git a/intra/src/main/java/com/hoker/intra/data/NfcAControllerImpl.kt b/intra/src/main/java/com/hoker/intra/data/NfcAControllerImpl.kt index aa656dc..14b387a 100644 --- a/intra/src/main/java/com/hoker/intra/data/NfcAControllerImpl.kt +++ b/intra/src/main/java/com/hoker/intra/data/NfcAControllerImpl.kt @@ -204,6 +204,10 @@ class NfcAControllerImpl @Inject constructor( } } + override fun getMaxTransceiveLength(): Int? { + return nfcA?.maxTransceiveLength + } + override suspend fun getNdefCapacity(ndef: Ndef): OperationResult { return try { val result = ndef.maxSize diff --git a/intra/src/main/java/com/hoker/intra/data/NfcVControllerImpl.kt b/intra/src/main/java/com/hoker/intra/data/NfcVControllerImpl.kt index 589726f..4fa92cb 100644 --- a/intra/src/main/java/com/hoker/intra/data/NfcVControllerImpl.kt +++ b/intra/src/main/java/com/hoker/intra/data/NfcVControllerImpl.kt @@ -175,9 +175,6 @@ class NfcVControllerImpl @Inject constructor( OperationResult.Failure() } - val nfcV = NfcV.get(tag) - nfcV.connect() - // truncate challenge to 10 bytes // challenge string into hex val challengeBytes: ByteArray = @@ -199,8 +196,7 @@ class NfcVControllerImpl @Inject constructor( challengeBytes.copyInto(command, UID_BYTE_LENGTH + 5, 0) // connect and send command Log.i("Command", Hex.encodeHexString(command)) - val response = nfcV.transceive(command) - nfcV.close() + val response = nfcV?.transceive(command) Log.i("Response", Hex.encodeHexString(response)) val sessionRequest = SessionRequest( @@ -231,6 +227,10 @@ class NfcVControllerImpl @Inject constructor( } } + override fun getMaxTransceiveLength(): Int? { + return nfcV?.maxTransceiveLength + } + override suspend fun getNdefMessage(ndef: Ndef): OperationResult { return try { val result = ndef.cachedNdefMessage diff --git a/intra/src/main/java/com/hoker/intra/di/NfcModule.kt b/intra/src/main/java/com/hoker/intra/di/NfcModule.kt index 38cdc1f..57eb95b 100644 --- a/intra/src/main/java/com/hoker/intra/di/NfcModule.kt +++ b/intra/src/main/java/com/hoker/intra/di/NfcModule.kt @@ -57,8 +57,11 @@ abstract class NfcModule { @Provides @Singleton - fun provideNfcAdapterController(nfcAdapter: NfcAdapter?): NfcAdapterController { - return NfcAdapterController(nfcAdapter) + fun provideNfcAdapterController( + nfcAdapter: NfcAdapter?, + nfcControllerFactory: NfcControllerFactory + ): NfcAdapterController { + return NfcAdapterController(nfcAdapter, nfcControllerFactory) } } diff --git a/intra/src/main/java/com/hoker/intra/domain/NfcActivity.kt b/intra/src/main/java/com/hoker/intra/domain/NfcActivity.kt index d94ecaf..780dd1d 100644 --- a/intra/src/main/java/com/hoker/intra/domain/NfcActivity.kt +++ b/intra/src/main/java/com/hoker/intra/domain/NfcActivity.kt @@ -4,6 +4,7 @@ import android.os.Bundle import android.os.PersistableBundle import android.widget.Toast import androidx.activity.ComponentActivity +import com.hoker.intra.di.NfcModule import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.delay import javax.inject.Inject @@ -12,6 +13,7 @@ import javax.inject.Inject abstract class NfcActivity : ComponentActivity() { @Inject lateinit var nfcAdapterController: NfcAdapterController + @Inject lateinit var nfcControllerFactory: NfcModule.NfcControllerFactory override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) { super.onCreate(savedInstanceState, persistentState) diff --git a/intra/src/main/java/com/hoker/intra/domain/NfcAdapterController.kt b/intra/src/main/java/com/hoker/intra/domain/NfcAdapterController.kt index 451af5c..4e75026 100644 --- a/intra/src/main/java/com/hoker/intra/domain/NfcAdapterController.kt +++ b/intra/src/main/java/com/hoker/intra/domain/NfcAdapterController.kt @@ -5,15 +5,17 @@ import android.nfc.NfcAdapter import android.nfc.Tag import android.os.Bundle import android.util.Log +import com.hoker.intra.di.NfcModule import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.receiveAsFlow import javax.inject.Inject open class NfcAdapterController @Inject constructor( - private val nfcAdapter: NfcAdapter? + private val nfcAdapter: NfcAdapter?, + private val nfcControllerFactory: NfcModule.NfcControllerFactory ) { - private var onTagDiscoveredListener: ((Tag?) -> Unit)? = null - private val listenerMap = LinkedHashMap Unit>>() + private var onTagDiscoveredListener: ((Tag, NfcController) -> Unit)? = null + private val listenerMap = LinkedHashMap Unit>>() private val _onScanChannel = Channel(Channel.BUFFERED) val scanEvent = _onScanChannel.receiveAsFlow() @@ -26,8 +28,15 @@ open class NfcAdapterController @Inject constructor( adapter.enableReaderMode( activity, { tag -> - _onScanChannel.trySend(Unit) - onTagDiscoveredListener?.invoke(tag) + when (val result = nfcControllerFactory.getController(tag)) { + is OperationResult.Success -> { + _onScanChannel.trySend(Unit) + onTagDiscoveredListener?.invoke(tag, result.data) + } + is OperationResult.Failure -> { + Log.i(this@NfcAdapterController::class.simpleName, "There was an error constructing the NfcController") + } + } }, flags, options @@ -46,7 +55,7 @@ open class NfcAdapterController @Inject constructor( fun setOnTagDiscoveredListener( uuid: String, className: String, - listener: (Tag?) -> Unit + listener: (Tag, NfcController) -> Unit ) { listenerMap.remove(uuid) listenerMap[uuid] = className to listener @@ -54,6 +63,13 @@ open class NfcAdapterController @Inject constructor( logCurrentListeners() } + fun setOnTagDiscoveredListener( + listener: (Tag, NfcController) -> Unit + ) { + listenerMap.clear() + onTagDiscoveredListener = listener + } + fun removeOnTagDiscoveredListener(uuid: String) { listenerMap.remove(uuid) updateListener() diff --git a/intra/src/main/java/com/hoker/intra/domain/NfcController.kt b/intra/src/main/java/com/hoker/intra/domain/NfcController.kt index c906ee9..dcb7e78 100644 --- a/intra/src/main/java/com/hoker/intra/domain/NfcController.kt +++ b/intra/src/main/java/com/hoker/intra/domain/NfcController.kt @@ -19,6 +19,7 @@ interface NfcController { suspend fun getNdefCapacity(ndef: Ndef): OperationResult suspend fun getNdefMessage(ndef: Ndef): OperationResult suspend fun checkConnection(): OperationResult + fun getMaxTransceiveLength(): Int? suspend fun withNdefConnection(tag: Tag, operations: suspend (ndef: Ndef) -> Unit): OperationResult { return try { close() diff --git a/intra/src/main/java/com/hoker/intra/domain/NfcViewModel.kt b/intra/src/main/java/com/hoker/intra/domain/NfcViewModel.kt index 65fb0d8..75fce08 100644 --- a/intra/src/main/java/com/hoker/intra/domain/NfcViewModel.kt +++ b/intra/src/main/java/com/hoker/intra/domain/NfcViewModel.kt @@ -2,12 +2,10 @@ package com.hoker.intra.domain import android.nfc.Tag import androidx.lifecycle.ViewModel -import com.hoker.intra.di.NfcModule import java.util.UUID abstract class NfcViewModel( private val nfcAdapterController: NfcAdapterController, - private val nfcControllerFactory: NfcModule.NfcControllerFactory, setAsActiveOnInjection: Boolean = true, ): ViewModel() { @@ -23,13 +21,8 @@ abstract class NfcViewModel( } fun setAsActiveListener() { - nfcAdapterController.setOnTagDiscoveredListener(uuid, className) { tag -> - tag?.let { - val nfcControllerResult = nfcControllerFactory.getController(tag) - if (nfcControllerResult is OperationResult.Success) { - onNfcTagDiscovered(tag, nfcControllerResult.data) - } - } + nfcAdapterController.setOnTagDiscoveredListener(uuid, className) { tag, nfcController -> + onNfcTagDiscovered(tag, nfcController) } }