├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── codexpedia │ │ └── nfcreadwrite │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── codexpedia │ │ │ └── nfcreadwrite │ │ │ └── MainActivity.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night │ │ └── themes.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ └── nfc_tech_filter.xml │ └── test │ └── java │ └── com │ └── codexpedia │ └── nfcreadwrite │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | #built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | 15 | # Local configuration file (sdk path, etc) 16 | local.properties 17 | 18 | # Windows thumbnail db 19 | Thumbs.db 20 | 21 | # OSX files 22 | .DS_Store 23 | 24 | # Eclipse project files 25 | .classpath 26 | .project 27 | 28 | # Android Studio 29 | *.iml 30 | .idea 31 | #.idea/workspace.xml - remove # and delete .idea if it better suit your needs. 32 | .gradle 33 | build/ 34 | 35 | #NDK 36 | obj/ 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android NFC read and write example 2 | 3 | http://www.codexpedia.com/android/android-nfc-read-and-write-example/ 4 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | } 5 | 6 | android { 7 | compileSdk 31 8 | 9 | defaultConfig { 10 | applicationId "com.codexpedia.nfcreadwrite" 11 | minSdk 21 12 | targetSdk 31 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | kotlinOptions { 30 | jvmTarget = '1.8' 31 | } 32 | buildFeatures { 33 | viewBinding true 34 | } 35 | } 36 | 37 | dependencies { 38 | 39 | implementation 'androidx.core:core-ktx:1.7.0' 40 | implementation 'androidx.appcompat:appcompat:1.4.1' 41 | implementation 'com.google.android.material:material:1.5.0' 42 | implementation 'androidx.constraintlayout:constraintlayout:2.1.3' 43 | testImplementation 'junit:junit:4.13.2' 44 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 45 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 46 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/codexpedia/nfcreadwrite/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.codexpedia.nfcreadwrite 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.codexpedia.nfcreadwrite", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/codexpedia/nfcreadwrite/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.codexpedia.nfcreadwrite 2 | 3 | import android.app.Activity 4 | import android.app.PendingIntent 5 | import android.content.Intent 6 | import android.content.IntentFilter 7 | import android.nfc.* 8 | import android.nfc.tech.Ndef 9 | import android.os.Bundle 10 | import android.os.Parcelable 11 | import android.util.Log 12 | import android.widget.Button 13 | import android.widget.TextView 14 | import android.widget.Toast 15 | import com.codexpedia.nfcreadwrite.databinding.ActivityMainBinding 16 | import java.io.IOException 17 | import java.io.UnsupportedEncodingException 18 | import java.nio.charset.Charset 19 | import kotlin.experimental.and 20 | 21 | class MainActivity : Activity() { 22 | 23 | lateinit var writeTagFilters: Array 24 | private lateinit var tvNFCContent: TextView 25 | private lateinit var message: TextView 26 | private lateinit var btnWrite: Button 27 | private lateinit var binding: ActivityMainBinding 28 | var nfcAdapter: NfcAdapter? = null 29 | var pendingIntent: PendingIntent? = null 30 | var writeMode = false 31 | var myTag: Tag? = null 32 | 33 | public override fun onCreate(savedInstanceState: Bundle?) { 34 | super.onCreate(savedInstanceState) 35 | binding = ActivityMainBinding.inflate(layoutInflater) 36 | setContentView(binding.root) 37 | 38 | tvNFCContent = binding.nfcContents 39 | message = binding.editMessage 40 | btnWrite = binding.button 41 | 42 | btnWrite.setOnClickListener { 43 | try { 44 | if (myTag == null) { 45 | Toast.makeText(this, ERROR_DETECTED, Toast.LENGTH_LONG).show() 46 | } else { 47 | write(message.text.toString(), myTag) 48 | Toast.makeText(this, WRITE_SUCCESS, Toast.LENGTH_LONG).show() 49 | } 50 | } catch (e: IOException) { 51 | Toast.makeText(this, WRITE_ERROR, Toast.LENGTH_LONG).show() 52 | e.printStackTrace() 53 | } catch (e: FormatException) { 54 | Toast.makeText(this, WRITE_ERROR, Toast.LENGTH_LONG).show() 55 | e.printStackTrace() 56 | } 57 | } 58 | 59 | nfcAdapter = NfcAdapter.getDefaultAdapter(this) 60 | if (nfcAdapter == null) { 61 | // Stop here, we definitely need NFC 62 | Toast.makeText(this, "This device doesn't support NFC.", Toast.LENGTH_LONG).show() 63 | finish() 64 | } 65 | 66 | //For when the activity is launched by the intent-filter for android.nfc.action.NDEF_DISCOVERE 67 | readFromIntent(intent) 68 | pendingIntent = PendingIntent.getActivity( 69 | this, 70 | 0, 71 | Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 72 | 0 73 | ) 74 | val tagDetected = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED) 75 | tagDetected.addCategory(Intent.CATEGORY_DEFAULT) 76 | writeTagFilters = arrayOf(tagDetected) 77 | } 78 | 79 | /****************************************************************************** 80 | * Read From NFC Tag 81 | ****************************************************************************/ 82 | private fun readFromIntent(intent: Intent) { 83 | val action = intent.action 84 | if (NfcAdapter.ACTION_TAG_DISCOVERED == action || NfcAdapter.ACTION_TECH_DISCOVERED == action || NfcAdapter.ACTION_NDEF_DISCOVERED == action) { 85 | myTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG) as Tag? 86 | val rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES) 87 | var msgs = mutableListOf() 88 | if (rawMsgs != null) { 89 | for (i in rawMsgs.indices) { 90 | msgs.add(i, rawMsgs[i] as NdefMessage) 91 | } 92 | buildTagViews(msgs.toTypedArray()) 93 | } 94 | } 95 | } 96 | 97 | private fun buildTagViews(msgs: Array) { 98 | if (msgs == null || msgs.isEmpty()) return 99 | var text = "" 100 | val payload = msgs[0].records[0].payload 101 | val textEncoding: Charset = if ((payload[0] and 128.toByte()).toInt() == 0) Charsets.UTF_8 else Charsets.UTF_16 // Get the Text Encoding 102 | val languageCodeLength: Int = (payload[0] and 51).toInt() // Get the Language Code, e.g. "en" 103 | try { 104 | // Get the Text 105 | text = String( 106 | payload, 107 | languageCodeLength + 1, 108 | payload.size - languageCodeLength - 1, 109 | textEncoding 110 | ) 111 | } catch (e: UnsupportedEncodingException) { 112 | Log.e("UnsupportedEncoding", e.toString()) 113 | } 114 | tvNFCContent.text = "Message read from NFC Tag:\n $text" 115 | } 116 | 117 | /****************************************************************************** 118 | * Write to NFC Tag 119 | ****************************************************************************/ 120 | @Throws(IOException::class, FormatException::class) 121 | private fun write(text: String, tag: Tag?) { 122 | val records = arrayOf(createRecord(text)) 123 | val message = NdefMessage(records) 124 | // Get an instance of Ndef for the tag. 125 | val ndef = Ndef.get(tag) 126 | // Enable I/O 127 | ndef.connect() 128 | // Write the message 129 | ndef.writeNdefMessage(message) 130 | // Close the connection 131 | ndef.close() 132 | } 133 | 134 | @Throws(UnsupportedEncodingException::class) 135 | private fun createRecord(text: String): NdefRecord { 136 | val lang = "en" 137 | val textBytes = text.toByteArray() 138 | val langBytes = lang.toByteArray(charset("US-ASCII")) 139 | val langLength = langBytes.size 140 | val textLength = textBytes.size 141 | val payload = ByteArray(1 + langLength + textLength) 142 | 143 | // set status byte (see NDEF spec for actual bits) 144 | payload[0] = langLength.toByte() 145 | 146 | // copy langbytes and textbytes into payload 147 | System.arraycopy(langBytes, 0, payload, 1, langLength) 148 | System.arraycopy(textBytes, 0, payload, 1 + langLength, textLength) 149 | return NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, ByteArray(0), payload) 150 | } 151 | 152 | /** 153 | * For reading the NFC when the app is already launched 154 | */ 155 | override fun onNewIntent(intent: Intent) { 156 | setIntent(intent) 157 | readFromIntent(intent) 158 | if (NfcAdapter.ACTION_TAG_DISCOVERED == intent.action) { 159 | myTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG) 160 | } 161 | } 162 | 163 | public override fun onPause() { 164 | super.onPause() 165 | WriteModeOff() 166 | } 167 | 168 | public override fun onResume() { 169 | super.onResume() 170 | WriteModeOn() 171 | } 172 | 173 | /****************************************************************************** 174 | * Enable Write and foreground dispatch to prevent intent-filter to launch the app again 175 | ****************************************************************************/ 176 | private fun WriteModeOn() { 177 | writeMode = true 178 | nfcAdapter!!.enableForegroundDispatch(this, pendingIntent, writeTagFilters, null) 179 | } 180 | 181 | /****************************************************************************** 182 | * Disable Write and foreground dispatch to allow intent-filter to launch the app 183 | ****************************************************************************/ 184 | private fun WriteModeOff() { 185 | writeMode = false 186 | nfcAdapter!!.disableForegroundDispatch(this) 187 | } 188 | 189 | companion object { 190 | const val ERROR_DETECTED = "No NFC tag detected!" 191 | const val WRITE_SUCCESS = "Text written to the NFC tag successfully!" 192 | const val WRITE_ERROR = "Error during writing, is the NFC tag close enough to your device?" 193 | } 194 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 13 | 18 | 19 | 25 |