How to transfer plain text via Android NFC? - android

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

Related

Send data from my App to Stm32 bluetooth Device - Kotlin

i have an application, and my application can connect to a bluetooth device.
After that, i want to send message (Int) to my Blutooth Low Energy device.
I have this code, but i can't figure it out what is the problem.
If you want i have : Characteristic UUID, Service UUID.
Really, i need your help...
I've edited the question :
My code :
val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
lateinit var bluetoothAdapter: BluetoothAdapter
val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
bluetoothAdapter = bluetoothManager.adapter
settingViewModel.bluetooth(bluetoothAdapter = bluetoothAdapter)
val mReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
val action = intent.action
if (action == BluetoothAdapter.ACTION_STATE_CHANGED) {
val state = intent.getIntExtra(
BluetoothAdapter.EXTRA_STATE,
BluetoothAdapter.ERROR
)
when (state) {
BluetoothAdapter.STATE_OFF -> {
settingViewModel.setIsConnected(false)
//settingViewModel.stopScan()
settingViewModel.setListDevices(null)
}
BluetoothAdapter.STATE_ON -> {
settingViewModel.setIsConnected(true)
//scan()
settingViewModel.setListDevices(bluetoothAdapter.bondedDevices)
context!!.unregisterReceiver(this)
}
}
}
}
}
context.registerReceiver(mReceiver, filter)
val SERVICE_UUID = "00000000-0001-11e1-9ab4-0002a5d5c51c"
val ConfigCharacteristic = descriptorOf(
service = SERVICE_UUID,
characteristic = "00E00000-0001-11e1-ac36-0002a5d5c51b",
descriptor = "00000000-0000-0000-0000-000000000000",
)
Button(
onClick = {
if (settingViewModel.isConnected.value == true) {
coroutine.launch(Dispatchers.IO) {
try {
settingViewModel.peripheral.write(ConfigCharacteristic, byteArrayOf(1))
} catch (e: Exception) {
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
}
}
}
// try {
// val Service =
// settingViewModel.deviceSocket.value.get .getService(UUID.fromString("0000ffe0-0000-1000-8000-00805f9b34fb"))
// val charac: BluetoothGattCharacteristic =
// Service.getCharacteristic(UUID.fromString("00E00000-0001-11e1-ac36-0002a5d5c51b"))
// settingViewModel.deviceSocket.value!!.outputStream.write("1".toByteArray())
// } catch (e: Exception) {
// Toast.makeText(context, e.message.toString(), Toast.LENGTH_LONG).show()
// }
}
) {
Text(text = "HelloWorld")
}
I Already have the mac adress, the caracteristic and the service UUID of the device i want to connect to.
Again, i really need your help
First of all:
When developing an app for a BLE device it is best to first use a generic BLE scanner app to test the connection and to find out which commands need to be sent. If you confirm that the BLE device works as expected you can continue with your own custom app. I would recommend nRF Connect for this task.
Regarding your problem:
There are still many things missing from your sourcecode. You said you can connect to the device but have problems sending a message. Your code does not contain anything related to a BLE connection so I can only assume that you connected to the device using the Bluetooth settings of your phone. This would be correct for Bluetooth Classic but BLE requires you to connect through your own custom app.
The Ultimate Guide to Android Bluetooth Low Energy explains all steps necessary for a successful BLE connection. These steps are:
Setting the correct permissions
Scan for nearby BLE devices
Connect to a BLE device of your choosing
Scan for Services
Read and Write a characteristic of your choosing
All these steps are explained in the Guide using Kotlin as programming language.

Using CompanionDeviceManager to read the devices information

I am using BluetoothLeScanner to scan for BLE devices and get a list of objects representing the devices to show inside my app (not connecting to any of them).
I am interested in doing the same, but using the CompanionDeviceManager now. Its callback CompanionDeviceManager.Callback.onDeviceFound(chooserLauncher: IntentSender?) unfortunately does not return any human readable form of found devices... the closest it gets is the IntentSender.writeToParcel method, but I am not sure how to use it in this situation.
I am not constrained to use the CompanionDeviceManager but I wanted to follow the OS version specific guidelines, we are supposed to use CompanionDeviceManager for Bluetooth devices scanning starting from API 26, but it seems useless in my case... so is there any way to get devices data from that callback, or should I just ditch it and stay with BluetoothLeScanner for all OS versions?
Late answer but it might help someone else. You can create a bluetooth device picker in combination with ActivityResultContracts.StartIntentSenderForResult() in order to get the BluetoothDevice. From there you will have access to all the device info that you need. Recent changes added some Android 12 permissions like android.permission.BLUETOOTH_CONNECT. Your mileage may vary.
val context = LocalContext.current
// Get the device manager instance
val deviceManager: CompanionDeviceManager by lazy {
ContextCompat.getSystemService(
context,
CompanionDeviceManager::class.java
) as CompanionDeviceManager
}
// Create a filter of your choice. Here I just look for specific device names
val deviceFilter: BluetoothDeviceFilter by lazy {
BluetoothDeviceFilter.Builder()
.setNamePattern(Pattern.compile(supportedDevices))
.build()
}
// Create a pairing request with your filter from the last step
val pairingRequest: AssociationRequest = AssociationRequest.Builder()
.addDeviceFilter(deviceFilter)
.build()
// Create a picker for discovered bluetooth devices
val bluetoothDevicePicker = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartIntentSenderForResult(),
onResult = {
val device: BluetoothDevice? =
it.data?.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE)
try {
// Now that you have the desired device, do what you need to with it
device?.apply {
when {
name?.matches(Regex(firstDevicePattern)) == true -> {
Log.i(TAG, "${this.name} connected")
onFirstDeviceDiscovered(device)
}
name?.matches(Regex(secondDevicePattern)) == true -> {
Log.i(TAG, "${this.name} connected")
onSecondDeviceDiscovered(device)
}
}
}
} catch (e: SecurityException) {
e.printStackTrace()
//TODO: handle the security exception (this is possibly a bug)
// https://issuetracker.google.com/issues/198986283
}
}
)
// A utility function to centralize calling associate (optional)
val associateDevice: (AssociationRequest) -> Unit = { request ->
// Attempt to associate device(s)
deviceManager.associate(
request,
object : CompanionDeviceManager.Callback() {
override fun onDeviceFound(chooserLauncher: IntentSender) {
val sender = IntentSenderRequest.Builder(chooserLauncher)
.build()
bluetoothDevicePicker.launch(sender)
}
override fun onFailure(error: CharSequence?) {
//TODO: handle association failure
}
}, null
)
}

Handling incoming bluetooth data stream in Kotlin Android app

I am working on a small app that connects via bluetooth to an Arduino with a bluetooth shield attached. My bluetooth connection is fine and I'm able to send commands from my app to the Arduino. I'm doing this in Kotlin. I'm learning as I go, so I'm misunderstanding something. Which is where I hope someone can point me in the right direction.
You can assume that all the bluetooth connection stuff is working fine(it is).
This is the part of my code that handles the sending of data to the Arduino.
private fun writeDataSendToMothership(outputToBt: String) {
try {
bluetoothSocket.outputStream.write(outputToBt.toByteArray())
Log.i(LOGTAG, "Button clicked, info sent: $outputToBt")
} catch (e: IOException) {
e.printStackTrace()
}
}
button_led_on.setOnClickListener { writeDataSendToMothership("1")}
button_led_off.setOnClickListener { writeDataSendToMothership("0")}
The part i'm having trouble with is receiving data from the Arduino(Mothership) and doing something with it. I cannot figure out what I need to do.
What I am trying to do is show an image in the app depending on what the Arduino sends after a button on the Arduino is pushed.
What I have so far is:
private fun readDataFromMothership(inputFromBt: String) {
try {
bluetoothSocket.inputStream.read(inputFromBt.toByteArray())
Log.i(LOGTAG, "Incoming data from Mothership recieved: $inputFromBt")
} catch (e: IOException) {
e.printStackTrace()
}
}
private fun View.showOrInvisible(imageShow: Boolean) {
visibility = if (imageShow) {
View.VISIBLE
} else {
View.INVISIBLE
}
}
This is where I fall flat.
if (readDataFromMothership()) {
imageView_mothership_button_pushed.showOrInvisible(true)
} else {
imageView_mothership_button_pushed.showOrInvisible(false)
}
I've left out anything from that function call. I've tried many different things, but I'm just not understanding what parameter I need, or am I way off. Am I even in the right neighborhood?
EDIT Other than my lack of general knowledge about programming, I think my hangup has to do with what to do with the "inputFromBt" String. Do I need to use a buffer of some sort. I'm trying/researching/reading up on everything I can. But stalling out.
Here is the code I have in place and currently working in my app:
private fun readBlueToothDataFromMothership(bluetoothSocket: BluetoothSocket) {
Log.i(LOGTAG, Thread.currentThread().name)
val bluetoothSocketInputStream = bluetoothSocket.inputStream
val buffer = ByteArray(1024)
var bytes: Int
//Loop to listen for received bluetooth messages
while (true) {
try {
bytes = bluetoothSocketInputStream.read(buffer)
val readMessage = String(buffer, 0, bytes)
liveData.postValue(readMessage)
} catch (e: IOException) {
e.printStackTrace()
break
}
}
}
// display or don't star image
private fun View.showOrHideImage(imageShow: Boolean) {
visibility = if (imageShow) View.VISIBLE else View.GONE
}
I mentioned in a comment to user frnnd, My main issue was the data being sent from my arduino. I was using println() instead of print() and that newline was messing things up.

Bluetooth can't find custom service UUID in SDP query

I've got a bit of my app that is dedicated to sharing files between devices over bluetooth using a quick, ad-hoc protocol that I put together. Currently, in the containing Activity I begin discovery, and add any device that I find into a RecyclerView. Here is the code for the BroadcastReceiver that is handling that:
private val scanReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == BluetoothDevice.ACTION_FOUND) {
val dev = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
Log.d(TAG, "Got device ${dev.name} with address ${dev.address}")
if (dev.name != null) {
Log.d(TAG, "Found nonnull device name, adding")
if (!viewAdapter.dataset.any { it.name == dev.name }) {
viewAdapter.dataset.add(dev)
viewAdapter.notifyDataSetChanged()
}
}
}
}
}
I wanted to modify this in such a way that it would only add devices who were broadcasting with the service UUID that I set up in the server portion of the app. After doing some research I came to this method that I could use to get the UUIDs of the services on the device. I integrated that into my BroadcastReceiver as such
private val scanReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
BluetoothDevice.ACTION_FOUND -> {
val dev = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
Log.d(TAG, "Got device ${dev.name} with address ${dev.address}")
if (dev.name != null) {
dev.fetchUuidsWithSdp()
}
}
//TODO: Untested code
BluetoothDevice.ACTION_UUID -> {
val id = intent.getParcelableExtra<ParcelUuid>(BluetoothDevice.EXTRA_UUID)
if (id.uuid == ShareServerSocket.SERVICE_UUID) {
val dev = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
if (!viewAdapter.dataset.any { it.name == dev.name }) {
viewAdapter.dataset.add(dev)
viewAdapter.notifyDataSetChanged()
}
}
}
}
}
}
(With requisite modifications to the IntentFilter I'm registering it with).
The code in the new branch gets called, I validated that with some debugging output. However, the ParcelUuid[] that I am given never contains the UUID of my service, and the device therefore never gets added. If I keep the entire setup the same on the device acting as a server, and bypass the new check on the client, I am able to connect and interact just fine. I'm unsure as to why my service wouldn't be being shown at this point.
P.S. I did also check the SDP cache, my service UUID is not there, either.
It turns out I was running into the same issue as described in Strange UUID reversal from fetchUuidsWithSdp. Stealing that workaround made it work.

How to direct users for enabling accessibility service for my app

I know It's impossible to enable the Accessibility service for apps programmatically, so I'd like to direct users to this screen:
System settings --> Accessibility --> app name --> enable/disable screen.
Is that possible ?
You can get them to the Accessibility screen on most devices using ACTION_ACCESSIBILITY_SETTINGS. However:
that may not work on all devices, so you will want to just send them to Settings as a fallback, if you get an ActivityNotFoundException
there is no way to get them straight to any given app, let alone the enable/disable screen
You can at least make it reach the app, making the app item blink. It should work for most devices, or at least those that are like of Pixel devices:
fun <T : AccessibilityService> getRequestAccessibilityPermissionIntents(context: Context, accessibilityService: Class<T>): Array<Intent> {
var intent = Intent("com.samsung.accessibility.installed_service")
if (intent.resolveActivity(context.packageManager) == null) {
intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
}
val extraFragmentArgKey = ":settings:fragment_args_key"
val extraShowFragmentArguments = ":settings:show_fragment_args"
val bundle = Bundle()
val showArgs = "${context.packageName}/${accessibilityService.canonicalName!!}"
bundle.putString(extraFragmentArgKey, showArgs)
intent.putExtra(extraFragmentArgKey, showArgs)
intent.putExtra(extraShowFragmentArguments, bundle)
return arrayOf(intent, Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY))
}
Usage:
private fun requestAccessibilityPermission() {
getRequestAccessibilityPermissionIntents(this, MyAccessibilityService::class.java).forEach { intent ->
try {
startActivity(intent)
return
} catch (e: Exception) {
}
}
//TODO do something here in case it failed
}

Categories

Resources