This question already has answers here:
NDEF Message with HCE Android
(2 answers)
Closed 4 years ago.
I know that at the moment there is no API in IOS 11 to write data to NFC tags, but it's possible to read data from NFC tags and want to pass text from Android device to iPhone.
I assumed that it's possible to write NdefMessage and it will be received on IOS, but it just doesnt work for me. There is no Intent received when I start NFC scanning on IOS (using NfcActions application).
Source code of my main activity:
class MainActivity : AppCompatActivity() {
var nfc: NfcAdapter? = null
#SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
hintLabel.text = "Initializing..."
nfc = NfcAdapter.getDefaultAdapter(this)
if (nfc == null) hintLabel.text = "NFC is not available on this device"
else hintLabel.text = "NFC initialized"
}
override fun onResume() {
super.onResume()
nfc?.enableNFCInForeground(this, javaClass)
}
override fun onPause() {
super.onPause()
nfc?.disableForegroundDispatch(this)
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
val pathPrefix = "/testuser:nfctest"
val isSuccess = createNFCMessage(pathPrefix, "Hello World", intent)
if(isSuccess)
hintLabel.text = "Successfully wrote data"
else
hintLabel.text = "Couldnt write any data"
}
fun createNFCMessage(pathPrefix: String, payload: String, intent: Intent?) : Boolean {
val nfcRecord = NdefRecord(NdefRecord.TNF_EXTERNAL_TYPE, pathPrefix.toByteArray(), ByteArray(0), payload.toByteArray())
val nfcMessage = NdefMessage(arrayOf(nfcRecord))
intent?.let {
val tag = it.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
return writeMessageToTag(nfcMessage, tag)
}
return false
}
fun <T>NfcAdapter.enableNFCInForeground(activity: Activity, classType: Class<T>) {
val pendingIntent = PendingIntent.getActivity(activity, 0,
Intent(activity,classType).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0)
val filters = arrayOf(IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED))
val techList = arrayOf(arrayOf(Ndef::class.java.name), arrayOf(NdefFormatable::class.java.name))
this.enableForegroundDispatch(activity, pendingIntent, filters, techList)
}
private fun writeMessageToTag(nfcMessage: NdefMessage, tag: Tag?): Boolean {
// This functions writes given NdefMessage to the tag, if it's possible
// and returns whether it was successful or not
}
And I also added following permissions and intent filter to main manifest
<uses-permission android:name="android.permission.NFC" />
<uses-feature
android:name="android.hardware.nfc"
android:required="false" />
<application ...>
<activity ...>
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:host="ext"
android:pathPrefix="/testuser:nfctest"
android:scheme="vnd.android.nfc"
android:mimeType="text/plain"/>
</intent-filter>
</activity>
</application>
What am I doing wrong and how to properly pass data from Android device to iPhone using NFC?
Apparently the usual peer-2-peer way to send NDEF messages from one phone to another only works between 2 android devices.
To send a message from Android to iOS you need to use host card emulation.
Basically the Android phone needs to emulate a Tag 4 Type (based on NDEF Forum specification), for the iPhone to read the content.
Hope that gets you on the right track ...
For anyone who stuck on this issue, I have read NFCForum-TS-Type-4-Tag which proposed by #Michael Roland. The whole idea is correct. All you need is to simulate the process SEND and RECEIVED commands to convert the byte array to a NDEF message. I created two repositories, one conclude the whole package about converting string to a NDEF message and the other one is a iOS Reader NDEF TAG to verify whether Android HCE is correct or not.
So Good luck!
Also see the response by Michael Roland to NDEF Message with HCE Android.
Related
I'm trying to send data using the DataClient from a phone to a watch.
Things I looked out for:
same package name
no build flavors on both modules
added service to the wear modules manifest
same path prefix
same signing config
I tried this sample project and copied parts over to my project. I just can't find any issues with it.
The sample project ran fine on my hardware, interestingly enough it wasn't working in the emulator. Therefore I tested my app also only with my hardware. (Pixel 6 Pro & Pixel Watch)
The sending data part seems to be working, as it behaves the same way as the sample project does.
How I send data from the phone:
class WearDataManager(val context: Context) {
private val dataClient by lazy { Wearable.getDataClient(context) }
companion object {
private const val CLIENTS_PATH = "/clients"
private const val CLIENT_LIST_KEY = "clientlist"
}
fun sendClientList(clientList: MutableList<String>) {
GlobalScope.launch {
try {
val request = PutDataMapRequest.create(CLIENTS_PATH).apply {
dataMap.putStringArray(CLIENT_LIST_KEY, arrayOf("clientList, test"))
}
.asPutDataRequest()
.setUrgent()
val result = dataClient.putDataItem(request).await()
Log.d("TAG", "DataItem saved: $result")
} catch (cancellationException: CancellationException) {
throw cancellationException
} catch (exception: Exception) {
Log.d("TAG", "Saving DataItem failed: $exception")
}
}
}
}
This is how I'm receiving data on the watch:
class WearableListenerService: WearableListenerService() {
companion object {
const val CLIENTS_PATH = "/clients"
}
override fun onCreate() {
super.onCreate()
Log.d("testing", "STARTED SERVICE")
}
override fun onDataChanged(dataEvents: DataEventBuffer) {
super.onDataChanged(dataEvents)
Log.d("testing", "RECEIVED $dataEvents")
}
}
Surprisingly "STARTED SERVICE" does not appear in the log when I start the app on the watch. For my understanding that means that the system isn't aware of the listeners existance and didn't register it. So something must be wrong with the manifest below.
This is the service inside the manifest on the watch:
<service android:name=".wear.communication.WearableListenerService"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
<data
android:host="*"
android:pathPrefix="/clients"
android:scheme="wear" />
</intent-filter>
</service>
What am I missing here?
Turns out the sending part was the culprit after all. Be careful what scope you use or if you even want to use one at all. This function is being called inside of a worker in my code so it isn't an issue.
I completely modified the demo project above and with the help of this I found out why it wasn't working.
This is the working solution:
fun sendClientList(clientList: MutableList<String>) {
val request = PutDataMapRequest.create(CLIENTS_PATH).apply {
dataMap.putStringArray(CLIENT_LIST_KEY, arrayOf(clientList.joinToString()))
}
.asPutDataRequest()
.setUrgent()
val result = dataClient.putDataItem(request)
Log.d("TAG", "DataItem saved: $result")
}
We are currently using a zebra device for company asset management so we are developing a small prototype android app to scan RFID tags. I've read from the data wedge API that the app can get scanned output has an intent broadcast.
But the app is unable to receive any intents.
Device : Zebra MC33
Data wedge version : 7.3
I've tried using the following
Profile Settings:
Intent Action : my.prototype.app.SCAN
Intent Delivery Type: Broadcast Intent.
Intent Category: Default.
Added to Associated Apps
AndroidManifest.xml
<receiver
android:name=".ScanIntentReceiver"
android:enabled="true"
android:exported="true" />
ScanIntentReceiver.kt
abstract class ScanIntentReceiver : BroadcastReceiver() {
abstract fun onReceiveScan(data: ScannerOutput)
override fun onReceive(p0: Context?, p1: Intent?) {
Timber.d("S1: Broadcast Scan Intent Received.")
p0?.let { context ->
p1?.let { intent ->
when (intent.action) {
BuildConfig.intentAction -> {
try {
val data = parseData(intent, context)
Timber.d("Data received: $data")
onReceiveScan(data)
} catch (ex: Exception) {
Timber.d("Parsing error")
Timber.d(ex)
}
}
else -> {
Timber.d("No Suitable Action.")
}
}
}
}
}
}
Also tried using the "Send via Start Activity"
Profile Settings:
Intent Action : my.prototype.app.SCAN
Intent Delivery Type: Send via StartActivity.
Intent Category: Default.
Added to Associated Apps
AndroidManifest.xml
<activity
android:name=".activity.ScanActivity"
android:launchMode="singleTask">
<intent-filter>
<action android:name="${intentAction}" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
ScanActivity.kt
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
Timber.d("Received Intent via Activity.")
intent?.let {
try {
val data = parseData(it, this)
viewModel.processOutput(data)
} catch (ex: Exception) {
Timber.e(ex)
}
}
}
Any help is appreciated. Thanks in advance.
UPDATE:
private fun parseData(intent: Intent, ctx: Context): ScannerOutput {
val decodedSource =
intent.getStringExtra(ctx.getString(R.string.datawedge_intent_key_source))
val decodedData =
intent.getStringExtra(ctx.getString(R.string.datawedge_intent_key_data))
val decodedLabelType =
intent.getStringExtra(ctx.getString(R.string.datawedge_intent_key_label_type))
....
}
UPDATE:
val filter = IntentFilter()
filter.addCategory(Intent.CATEGORY_DEFAULT)
filter.addAction(BuildConfig.intentAction)
registerReceiver(scanIntentReceiver, filter)
Let's clarify things a bit. If you want to read RFID tags using the MC33R, then you must use Zebra RFID API3, not intents. Zebra is considering using the intents also for RFID, but at the moment the best option is to use the SDK, not the intent Broadcaster/Receiver. If you intend to use the barcode scanner, then the official (new) way of doing it is through intents. To get the intents you must configure a profile in the Data Wedge, you must activate intent broadcasing and specify the intent action in the profile, if you do so, you'll receive the intent. Look for the following settings in the data Wedge profile (default profile is good):
Intent Output = ON
Intent action = my.prototype.app.SCAN
Intent distribution (or delivery): Broadcast
I can assure you that these settings will work for the barcode scanner, but in case you want to use the RFID antenna, download the API3 SDK from Zebra Developer site and follow the examples.
***UPDATE
val filter = IntentFilter()
filter.addCategory(Intent.CATEGORY_DEFAULT)
filter.addAction("my.prototype.app.SCAN")//here set the action (same as in DataWedge app)
this.requireContext().registerReceiver(this, filter)
Implement
BroadcastReceiver
and add:
override fun onReceive(context: Context?, intent: Intent?) {
//Receives readings from barcode scanner
val action = intent!!.action
if (action == "my.prototype.app.SCAN") {
val decodedData = intent.getStringExtra("com.symbol.datawedge.data_string")
if (decodedData != null) {
//decodedData is your barcode
}
}
}
My implementation goal:
Nt3h2211 tag (configured over I2C from a PIC-mcu) then read by my Android App.
How to launch app by scanning NDEF NFC tag?
This thread seems very close to the problem I am experiencing.
Editing the manifest intent filters per those suggestions did not resolve my issue.
Any assistance is greatly appreciated.
I made the hardware implementation and wrote the PIC firmware; so, I have access to change the tag configuration if needed. Tag is formatted as NDEF and the payload is only bit fields… at this point only 16 bytes are populated. See pic of tag memory state as read from manufactures NFC Tag reading utility.
Tag configuration
Manifest file nfc intent filters:
<intent-filter>
<action android:name=”android.nfc.action.NDEF.DISCOVERED”/>
<category android:name=”android.intent.category.DEFAULT”/>
<data android:scheme=”vnd.android.nfc”
android:host=”ext”
android:pathPrefix=”/blackfeathersystems.com:pr”/>
</intent-filter>
<intent-filter>
<action android:name=”android.nfc.action.TECH.DISCOVERED”/>
<category android:name=”android.intent.category.DEFAULT”/>
<data android:scheme=”vnd.android.nfc”
android:host=”ext”
android:pathPrefix=”/blackfeathersystems.com:pr”/>
</intent-filter>
<meta-data android:name=”android.nfc.action.TECH_DISCOVERED”
android:resource=”#xml/nfc_tech_list” />
The nfc_tech_list xml file content:
<?xml version=”1.0” endoding=”utf-8”?>
<resources xmlns:xliff=”urn:oasis:names:tc:xliff:document:1.2”>
<tech-list>
<tech> android.nfc.tech.NfcA</tech>
</tech-list>
<tech-list>
<tech> android.nfc.tech.Ndef</tech>
</tech-list>
<tech-list>
<tech> android.nfc.tech.MifareClassic</tech>
</tech-list>
<tech-list>
<tech> android.nfc.tech.MifareUltralight</tech>
</tech-list>
</resources>
The main activity code. Please note the onNewIntent() function has been stubbed out with Toast’s for brevity – but it only ever resolves to “action: NOT DISCOVERED”
package com.example.nfc2211comms2
class MainActivity : AppCompatActivity() {
lateinit var nfcAdapter : NfcAdapter
lateinit var pendingIntent: PendingIntent
lateinit var nfcA: NfcA
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_fragment)
nfcAdapter = NfcAdapter.getDefaultAdapter(this)
} // end onCreate
override fun onResume() {
Val intent = Intent(this, javaClass).apply {
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
}
pendingIntent = PendingIntent.getActivity(this, 0, intent, 0)
nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null)
onNewIntent(intent)
} // end onResume
override fun onNewIntent(nintent: Intent) {
super.onNewIntent(intent)
if (NfcAdapter.ACTION_NDEF_DISCOVERED == nintent.action) {
Toast.makeText(this, “ action: NDEF ”, LENGTH_SHORT).show()
} else if (NfcAdapter.ACTION_TECH_DISCOVERED == nintent.action) {
Toast.makeText(this, “ action: TECH ”, LENGTH_SHORT).show()
} else {
Toast.makeText(this, “ Not Discovered ”, LENGTH_SHORT).show()
}
} // end onNewIntent
override fun onPause() {
super.onPause()
nfcA.close()
nfcAdapter.disableForegroundDispatch(this)
}
override fun onDestroy() {
super.onDestroy()
finish()
}
} // end mainActivity
I think I may have, at least partially, answered my own question.
After rewriting the code to be less verbose I found out
NDEF formatted tags need one of three first records:
SMART POSTER, 2)WELL KNOWN MIME or 3) URI
I have EXTERNAL, so the NDEF_DISCOVERED filter will never
get hit. Consequently the TECH_DISCOVERED intent does work.
Thanks!
I am new to Android NFC and developing NFC application in android. My idea is Device A need to send a plain text to Device B. Is it possible in Android NFC?
I just tried with Tag Dispatcher (enableForegroundDispatch , disableForegroundDispatch) on both Reader and Writer.
My Reader side code is :
nfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFilters, techList)
override fun onNewIntent(intent: Intent?) {
intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)?.also { rawMessages ->
val messages: List<NdefMessage> = rawMessages.map { it as NdefMessage }
for (message in messages) {
for (record in message.records) {
println(" ${record.toString()}")
}
}
}
}
My Writer side code is:
nfcAdapter.enableForegroundDispatch(
this, pendingIntent, intentFilters, techList)
override fun onNewIntent(intent: Intent?) {
if (action.equals(NfcAdapter.EXTRA_TAG)) {
val tagFromIntent = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)
|| NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)
) {
println("testing=============== tag discovered ")
writeNdefMessage(tagFromIntent!!, "This is my first app")
}
}}
private fun writeNdefMessage(tag: Tag, message: String) {
val record: NdefRecord = newTextRecord(message, Locale.ENGLISH, true)!!
val ndefMessage = NdefMessage(arrayOf(record))
try {
if (isExist(tag.techList, NdefFormatable::class.java.name)) {
val ndefFormatable = NdefFormatable.get(tag)
try {
if (!ndefFormatable.isConnected) {
ndefFormatable.connect()
}
ndefFormatable.format(ndefMessage)
} finally {
ndefFormatable.close()
}
} else if (isExist(tag.techList, Ndef::class.java.name)) {
val ndef = Ndef.get(tag)
try {
if (!ndef.isConnected) {
ndef.connect()
}
if (ndef.isWritable) {
ndef.writeNdefMessage(ndefMessage)
}
} finally {
ndef.close()
}
}
} catch (e: FormatException) {
println("Format failed exception")
} catch (e: IOException) {
println("")
}
}
Application is launched when I scan the Tag (via AndroidManifest.xml details). But I am not able to send plain text via NFC. I don't know what I did wrong. I don't know whether the approach is right or wrong. Please help me to proceed this.
Thanks in advance.
So in Android peer to peer NFC (Device to Device) also called Android Beam has been deprecated as of API 29
See https://developer.android.com/reference/android/nfc/NfcAdapter#setNdefPushMessage(android.nfc.NdefMessage,%20android.app.Activity,%20android.app.Activity...)
You are using the wrong methods to use Android Beam in older Android Versions.
See https://developer.android.com/guide/topics/connectivity/nfc/nfc#p2p for more details of actually how to use it. (You are using methods for writing to a NFC card not another Device)
Note Peer to Peer via NFC is Android only, iOS does not support it and it is depreciated in favour of Bluetooth/Wifi Direct
Note that it is still possible to have one Android Device use Host Card Emulation to Emulate a Type 4 NFC card with an NDEF messages on it but this is quite complicated to achieve.
Update:
Link to Host Card Emulation https://developer.android.com/guide/topics/connectivity/nfc/hce and Type 4 card spec http://apps4android.org/nfc-specifications/NFCForum-TS-Type-4-Tag_2.0.pdf
I am trying to Read Sms by using this method. But my application is not reading Message.
The Code i have tried yet.
Permission :
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.SEND_SMS" />
Activity (Main Code) :
class OtpActivity : AppCompatActivity(), View.OnClickListener {
private var smsVerifyCatcher: SmsVerifyCatcher? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_otp)
smsVerifyCatcher = SmsVerifyCatcher(this, OnSmsCatchListener { message ->
val code = parseCode(message)//Parse verification code
Log.e("Code", code)
//then you can send verification code to server
})
smsVerifyCatcher!!.setPhoneNumberFilter("0902249") // I passed 10 digit number here
smsVerifyCatcher!!.setFilter("Ashish") // For extra i added Filter for name
}
private fun parseCode(message: String): String {
val p = Pattern.compile("\\b\\d{4}\\b")
val m = p.matcher(message)
var code = ""
while (m.find()) {
code = m.group(0)
}
return code
}
override fun onStart() {
super.onStart()
smsVerifyCatcher!!.onStart()
}
override fun onStop() {
super.onStop()
smsVerifyCatcher!!.onStop()
}
}
It's not a good idea because of this Reminder SMS/Call Log Policy Changes.
The recomended way is using SMS Retriever API from Google Play Services. See the Automatic SMS Verification with the SMS Retriever API.
Notice though that your server needs to send the messages following a few rules (message starts with "<#>", includes the OTP plus additional information and ends up with a hash identifying your app).