Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [3.2.1] - 2026-02-06
### Changed
- Normalize logging.

## [3.2.0] - 2026-02-06
### Added
- Added `MIFARE_CLASSIC_1K` and `MIFARE_CLASSIC_4K` support in `AndroidNfcSupportedProtocols`.
Expand Down Expand Up @@ -89,7 +93,8 @@ This is the initial release.
It follows the extraction of Keyple 1.0 components contained in the `eclipse-keyple/keyple-java` repository to dedicated repositories.
It also brings many major API changes.

[unreleased]: https://github.com/eclipse-keyple/keyple-plugin-android-nfc-java-lib/compare/3.2.0...HEAD
[unreleased]: https://github.com/eclipse-keyple/keyple-plugin-android-nfc-java-lib/compare/3.2.1...HEAD
[3.2.1]: https://github.com/eclipse-keyple/keyple-plugin-android-nfc-java-lib/compare/3.2.0...3.2.1
[3.2.0]: https://github.com/eclipse-keyple/keyple-plugin-android-nfc-java-lib/compare/3.1.0...3.2.0
[3.1.0]: https://github.com/eclipse-keyple/keyple-plugin-android-nfc-java-lib/compare/3.0.0...3.1.0
[3.0.0]: https://github.com/eclipse-keyple/keyple-plugin-android-nfc-java-lib/compare/2.2.0...3.0.0
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
///////////////////////////////////////////////////////////////////////////////

plugins {
id("com.diffplug.spotless") version "7.0.4"
id("com.diffplug.spotless") version "8.2.1"
id("org.jetbrains.dokka") version "1.9.20"
}
buildscript {
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
group = org.eclipse.keyple
title = Keyple Plugin Android NFC Java Lib
description = Keyple add-on to manage Android NFC readers
version = 3.2.0-SNAPSHOT
version = 3.2.1-SNAPSHOT

# Java Configuration
javaSourceLevel = 1.8
Expand Down
22 changes: 16 additions & 6 deletions plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,14 @@ tasks {
.takeIf { it.exists() }
?.readText()
.orEmpty()
.trim())
.trim()
)
appendLine()
appendLine("<br>")
appendLine()
appendLine("> ${project.findProperty("javadoc.copyright") as String}")
})
}
)
}
}
dokkaHtml.configure {
Expand All @@ -146,7 +148,9 @@ tasks {
attributes(
mapOf(
"Implementation-Title" to "$title Sources",
"Implementation-Version" to project.version))
"Implementation-Version" to project.version,
)
)
}
}
}
Expand All @@ -159,7 +163,9 @@ tasks {
attributes(
mapOf(
"Implementation-Title" to "$title Documentation",
"Implementation-Version" to project.version))
"Implementation-Version" to project.version,
)
)
}
}
register<Jar>("javadocJar") {
Expand All @@ -172,7 +178,9 @@ tasks {
attributes(
mapOf(
"Implementation-Title" to "$title Documentation",
"Implementation-Version" to project.version))
"Implementation-Version" to project.version,
)
)
}
}
register("copyLicenseFiles") { doLast { copyLicenseFiles() } }
Expand Down Expand Up @@ -221,7 +229,9 @@ afterEvaluate {
mapOf(
"project.build.sourceEncoding" to "UTF-8",
"maven.compiler.source" to javaSourceLevel,
"maven.compiler.target" to javaTargetLevel))
"maven.compiler.target" to javaTargetLevel,
)
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,5 @@ data class AndroidNfcConfig(
val skipNdefCheck: Boolean = true,
val cardInsertionPollingInterval: Int = 0,
val cardRemovalPollingInterval: Int = 100,
val keyProvider: KeyProvider? = null
val keyProvider: KeyProvider? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import org.eclipse.keyple.core.plugin.storagecard.internal.KeyStorageType
import org.eclipse.keyple.core.plugin.storagecard.internal.spi.ApduInterpreterFactorySpi
import org.eclipse.keyple.core.plugin.storagecard.internal.spi.ApduInterpreterSpi
import org.eclipse.keyple.core.util.HexUtil
import org.eclipse.keyple.core.util.json.JsonUtil
import org.eclipse.keyple.plugin.android.nfc.spi.KeyProvider
import org.json.JSONObject
import org.slf4j.LoggerFactory
Expand All @@ -47,7 +48,11 @@ internal class AndroidNfcReaderAdapter(private val config: AndroidNfcConfig) :
CommandProcessorApi,
NfcAdapter.ReaderCallback {

private val logger = LoggerFactory.getLogger(this::class.java)
private companion object {
private val logger = LoggerFactory.getLogger(AndroidNfcReaderAdapter::class.java)
private const val MIFARE_KEY_A = 0x60
private const val MIFARE_KEY_B = 0x61
}

private val nfcAdapter: NfcAdapter = NfcAdapter.getDefaultAdapter(config.activity)
private val options: Bundle
Expand All @@ -67,12 +72,6 @@ internal class AndroidNfcReaderAdapter(private val config: AndroidNfcConfig) :
private lateinit var uid: ByteArray
private lateinit var powerOnData: String

private companion object {
private const val MIFARE_ULTRALIGHT_READ_SIZE = 16
private const val MIFARE_KEY_A = 0x60
private const val MIFARE_KEY_B = 0x61
}

init {
flags =
(if (config.skipNdefCheck) NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK else 0) or
Expand All @@ -81,7 +80,9 @@ internal class AndroidNfcReaderAdapter(private val config: AndroidNfcConfig) :
Bundle().apply {
if (config.cardInsertionPollingInterval > 0) {
putInt(
NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, config.cardInsertionPollingInterval)
NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY,
config.cardInsertionPollingInterval,
)
}
}
apduInterpreter =
Expand All @@ -92,22 +93,26 @@ internal class AndroidNfcReaderAdapter(private val config: AndroidNfcConfig) :
it.createApduInterpreter()
}
apduInterpreter?.setCommandProcessor(this)
logger.info("{}: config initialized: {}", name, config)
if (logger.isDebugEnabled) {
logger.debug("Reader initialized [config={}]", config)
}
}

override fun getName(): String = AndroidNfcConstants.READER_NAME

override fun openPhysicalChannel() {
if (tagTechnology!!.isConnected) {
logger.info("{}: card already connected", name)
if (logger.isDebugEnabled) {
logger.debug("Card already connected")
}
return
}
try {
tagTechnology!!.connect()
isCardChannelOpen = true
loadedKey = null
} catch (e: Exception) {
throw CardIOException("Error while opening physical channel", e)
throw CardIOException("Failed to open physical channel", e)
}
}

Expand All @@ -120,7 +125,7 @@ internal class AndroidNfcReaderAdapter(private val config: AndroidNfcConfig) :
}

override fun checkCardPresence(): Boolean {
throw UnsupportedOperationException("checkCardPresence() is not supported")
throw UnsupportedOperationException("checkCardPresence() method is not supported")
}

override fun getPowerOnData() = powerOnData
Expand All @@ -133,7 +138,7 @@ internal class AndroidNfcReaderAdapter(private val config: AndroidNfcConfig) :
apduInterpreter.processApdu(apduIn)
}
} catch (e: Exception) {
throw CardIOException("Error while transmitting APDU: ${e.message}", e)
throw CardIOException("Failed to transmit APDU", e)
}
}

Expand Down Expand Up @@ -179,8 +184,10 @@ internal class AndroidNfcReaderAdapter(private val config: AndroidNfcConfig) :
}

// For MIFARE Classic, check the actual card size to distinguish between 1K and 4K
if (protocol == AndroidNfcSupportedProtocols.MIFARE_CLASSIC_1K ||
protocol == AndroidNfcSupportedProtocols.MIFARE_CLASSIC_4K) {
if (
protocol == AndroidNfcSupportedProtocols.MIFARE_CLASSIC_1K ||
protocol == AndroidNfcSupportedProtocols.MIFARE_CLASSIC_4K
) {
val mifareClassic = tagTechnology as? MifareClassic ?: return false
return when (protocol) {
AndroidNfcSupportedProtocols.MIFARE_CLASSIC_1K ->
Expand All @@ -195,20 +202,30 @@ internal class AndroidNfcReaderAdapter(private val config: AndroidNfcConfig) :
}

override fun onStartDetection() {
logger.info("{}: start card detection", name)
if (logger.isDebugEnabled) {
logger.debug("Starting card detection")
}
try {
nfcAdapter.enableReaderMode(config.activity, this, flags, options)
if (logger.isDebugEnabled) {
logger.debug("Card detection started")
}
} catch (e: Exception) {
throw ReaderIOException("Failed to start NFC detection", e)
throw ReaderIOException("Failed to start card detection", e)
}
}

override fun onStopDetection() {
logger.info("{}: stop card detection", name)
if (logger.isDebugEnabled) {
logger.debug("Stopping card detection")
}
try {
nfcAdapter.disableReaderMode(config.activity)
if (logger.isDebugEnabled) {
logger.debug("Card detection stopped")
}
} catch (e: Exception) {
throw ReaderIOException("Failed to stop NFC detection", e)
throw ReaderIOException("Failed to stop card detection", e)
}
}

Expand All @@ -219,7 +236,7 @@ internal class AndroidNfcReaderAdapter(private val config: AndroidNfcConfig) :
override fun waitForCardRemoval() {
if (!isWaitingForCardRemoval) {
if (logger.isDebugEnabled) {
logger.debug("{}: waiting for card removal", name)
logger.debug("Waiting for card removal...")
}
isWaitingForCardRemoval = true
handler.post(tagPresenceChecker)
Expand Down Expand Up @@ -251,7 +268,7 @@ internal class AndroidNfcReaderAdapter(private val config: AndroidNfcConfig) :
tagTechnology?.isConnected == true
} catch (_: Exception) {
if (logger.isDebugEnabled) {
logger.debug("{}: card removed", name)
logger.debug("Card removed")
}
false
}
Expand All @@ -271,7 +288,8 @@ internal class AndroidNfcReaderAdapter(private val config: AndroidNfcConfig) :
is MifareUltralight -> adjustBufferLength(tech.readPages(blockAddress), length)
else ->
throw UnsupportedOperationException(
"Unsupported tag technology: ${tech?.let { it::class.java.simpleName } ?: "null"}")
"Unsupported tag technology: ${tech?.let { it::class.java.simpleName } ?: "null"}"
)
}
}

Expand All @@ -289,7 +307,8 @@ internal class AndroidNfcReaderAdapter(private val config: AndroidNfcConfig) :
is MifareUltralight -> tech.writePage(blockAddress, data)
else ->
throw UnsupportedOperationException(
"Unsupported tag technology: ${tech?.let { it::class.java.simpleName } ?: "null"}")
"Unsupported tag technology: ${tech?.let { it::class.java.simpleName } ?: "null"}"
)
}
}

Expand All @@ -300,14 +319,14 @@ internal class AndroidNfcReaderAdapter(private val config: AndroidNfcConfig) :
override fun generalAuthenticate(blockAddress: Int, keyType: Int, keyNumber: Int): Boolean {
val mifareClassic =
tagTechnology as? MifareClassic
?: throw CardIOException("General Authenticate is only supported for Mifare Classic.")
?: throw CardIOException("General Authenticate is only supported for Mifare Classic")

val key = loadedKey
loadedKey = null

val usedKey =
key
?: checkNotNull(keyProvider) { "No key loaded and no key provider available." }
?: checkNotNull(keyProvider) { "No key loaded and no key provider available" }
.getKey(keyNumber)
?: throw IllegalStateException("No key found for key number: $keyNumber")

Expand All @@ -316,12 +335,14 @@ internal class AndroidNfcReaderAdapter(private val config: AndroidNfcConfig) :
return when (keyType) {
MIFARE_KEY_A -> mifareClassic.authenticateSectorWithKeyA(sectorIndex, usedKey)
MIFARE_KEY_B -> mifareClassic.authenticateSectorWithKeyB(sectorIndex, usedKey)
else -> throw IllegalArgumentException("Unsupported key type: 0x${keyType.toString(16)}")
else -> throw IllegalArgumentException("Unsupported key type: 0x${HexUtil.toHex(keyType)}")
}
}

override fun onTagDiscovered(tag: Tag) {
logger.info("{}: card discovered: {}", name, tag)
if (logger.isDebugEnabled) {
logger.debug("Card detected [tag={}]", JsonUtil.toJson(tag))
}
isCardChannelOpen = false
try {
for (technology in tag.techList) when (technology) {
Expand Down
Loading