Enabling Foreground Dispatch (for NFC) in Unity 2022.1.17f - android

I'm currently trying to enable Foreground Dispatching for a Unity / board game I'm working on. My problem is that, although I do have example of how it was achieved/is achievable, Visual Studio won't recognize the "Android" namespace, thus not allowing me to fiddle with Android functionnalities such as Intent and NFC.
The code I am testing with is as follow :
using System;
using Xamarin.Essentials;
using static UnityEditor.PlayerSettings.Android;
using UnityEditor.Android;
using Android.App;
using Android.Content;
using Android.Nfc;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Unity.Android;
using UnityEngine.Android;
using UnityEngine;
using UnityEngine.XR;
public class NfcForegroundDispatch : MonoBehaviour
{
NfcAdapter nfcAdapter;
PendingIntent pendingIntent;
IntentFilter[] intentFiltersArray;
string[][] techListsArray;
void Start()
{
// Get the NFC adapter
nfcAdapter = NfcAdapter.GetDefaultAdapter(this);
if (nfcAdapter == null)
{
// NFC not available on this device
return;
}
// Create a pending intent to handle NFC intents
pendingIntent = PendingIntent.GetActivity(this, 0,
new Intent(this, typeof(NfcForegroundDispatch)).AddFlags(ActivityFlags.SingleTop), 0);
// Create an intent filter for NFC actions
IntentFilter ndefDetected = new IntentFilter(NfcAdapter.ActionNdefDiscovered);
try
{
ndefDetected.AddDataType("/");
}
catch (MalformedMimeTypeException)
{
throw new Exception("Check the MIME type");
}
// Assign the filters to the intent filter array
intentFiltersArray = new IntentFilter[] { ndefDetected };
// Specify the tech lists you want to support
techListsArray = new string[][] { new string[] { typeof(Android.Nfc.Tech.Ndef).Name } };
}
void OnResume()
{
// Enable foreground dispatch when the app is resumed
nfcAdapter.EnableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray);
}
void OnPause()
{
// Disable foreground dispatch when the app is paused
nfcAdapter.DisableForegroundDispatch(this);
}
void OnNewIntent(Intent intent)
{
// Handle the NFC intent
// ...
}
}
All the 'using Android' declarations are underlined in red if I open the script from Unity.
But if I open the script directly from VS Community, no errors show on screen.
This led me to believe that my problem was in the libraries used by Unity, but I just can't seem to find the right library for it. I already put the Xamarin.Essentials.dll file in the appropriate folder, but that didn't solve anything...
Am I correct about it being a library issue ? If so, what is the appropriate library I should be looking for ?
And if not, what am I doing wrong ? I would really appreciate someone pointing me in the right direction.
Thanks a lot!

Related

Should NfcAdapter.enableReaderMode in foreground Activity override the intent tag dispatch system?

In Android 10 I noticed I get a Toast message from the OS stating "No supported application for this NFC tag" or "No supported app for this NFC tag" (depending on the device):
The weird thing is that I see the Toast while enableReaderMode is active in the foreground Activity. In all previous versions of Android, enableReaderMode would override the Android intent tag dispatch system. Is this a bug in Android 10?
I know enableForegroundDispatch also exists, and that API does seem to override the intent tag dispatch system even in Android 10. But I'd like to keep control over the NFC discovery sound which is only provided by enableReaderMode.
I also know that I can declare an intent-filter in my manifest to get rid of the Toast while continuing to use enableReaderMode, but that also has unintended side effects (e.g. my app could be launched while reading the NFC tag from the device home screen which I don't want).
Yes, it seems to be a bug as Android OS fires a new tagDiscovery event under the following conditions:
Use enableReaderMode instead of enableForegroundDispatch
Read or write a card
While the card is still in proximity call disableReaderMode.
Since this triggers an OS level event, it can pause focused activity, might show a toast screen or show related application select box.
To workaround the problem,
Workaround 1:
Try connecting the card in a loop until an IOException is fired. (which means card is not in proximity anymore)
Then call disableReaderMode
Cons: You may need to show a message to the user to remove the tag/card from the device proximity.
Workaround 2:
Use legacy enableForegroundDispatch / disableForegroundDispatch along with readerMode
Cons: Popup do not gets displayed, however tag discovery sound still gets triggered.
Both solutions do not need intent-filter to be defined.
Sample code that implements both workarounds is below.
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.os.Bundle;
import android.widget.Toast;
import java.io.IOException;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity
{
private NfcAdapter nfcAdapter;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
//Assuming nfc adapter is present and active, such checks are ignored for code clarity,
//production code must check for hardware nfc adapter existence
nfcAdapter = NfcAdapter.getDefaultAdapter(this); //Get the default NFC adapter from OS
//Additional initialization code
}
private void onTagDiscovered(Tag tag)
{
try
{
if (tag == null)
return;
//Assumption: We're using an NFC card that supports IsoDep
IsoDep iso = IsoDep.get(tag);
if (iso == null)
return;
iso.setTimeout(1000);
iso.connect();
//doCardReadWrite(iso);
iso.close();
//Workaround 1
//Wait until the card has been removed from the range of NFC
//Then finish the activity
while(true)
{
try
{
iso.connect();
Thread.sleep(100);
iso.close();
}
catch (IOException | InterruptedException e)
{
//On this example, once we're done with the activity, we want to close it
//then onPause event will call disableReaderMode, at that moment we don't want a card to be in proximity
onCardRemoved();
break;
}
}
//End of Workaround 1
}
catch (IOException e)
{
Toast.makeText(this, "Tag disconnected. Reason: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
private void onCardRemoved()
{
this.finish();
}
#Override
protected void onResume()
{
super.onResume();
//Workaround 2
//Legacy nfc reader activation method, just enabled it but we won't use it
//(to fully support [e.g. OS version < v4.4.4] you must override onNewIntent(Intent intent) method as well)
//create intent/tag filters to be used with enableForegroundDispatch
PendingIntent pendingIntent = PendingIntent.getActivity(
this,
0,
new Intent(this, this.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
0
);
IntentFilter tagDetected = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
tagDetected.addCategory(Intent.CATEGORY_DEFAULT);
IntentFilter[] writeTagFilters = new IntentFilter[]{tagDetected};
nfcAdapter.enableForegroundDispatch(this, pendingIntent, writeTagFilters, null);
//End of Workaround 2
//ReaderMode activation
Bundle options = new Bundle();
options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 1000);//Presence check interval
final int READER_FLAGS = NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK | NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS;
nfcAdapter.enableReaderMode(this, new NfcAdapter.ReaderCallback()
{
#Override
public void onTagDiscovered(Tag tag)
{
MainActivity.this.onTagDiscovered(tag);
}
}, READER_FLAGS, options);
}
#Override
protected void onPause()
{
nfcAdapter.disableReaderMode(this);
//Workaround 2
nfcAdapter.disableForegroundDispatch(this);
super.onPause();
}
}

Passing extras from ANE to Flex app

I have a flex mobile application with an ANE. This ANE has a broadcast receiver that starts the flex mobile application when it receives an event:
public class BroadcastEventHandler extends BroadcastReceiver{
#Override
public void onReceive(Context context, Intent intent) {
Log.d(Constants.TAG, "BROADCAST EVENT RECEIVED!");
try {
Intent i = new Intent(context,
Class.forName(context.getPackageName()+".AppEntry"));
i.addCategory( Intent.CATEGORY_LAUNCHER );
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.putExtra("nameKey", "value");
context.startActivity(i);
} catch (ClassNotFoundException e) {
e.printStackTrace();
Log.d(Constants.TAG, "Error on starting Intent: "+e.getMessage());
}
}
On the flex application I have the following code:
protected function view1_preinitializeHandler(event:FlexEvent):void
{
NativeApplication.nativeApplication.addEventListener(
InvokeEvent.INVOKE, onInvoke);
}
private function onInvoke(event:InvokeEvent):void
{
trace("Arguments: " + event.arguments);
}
What I want to do is to pass Extras from the broadcastreceiver to the flex application when it is executed (as you can see I added a Bundle object in the ANE code, but I don't receive anything in the flex application):
Trace:
Arguments:
Do you know a way to start activity (in android native) with some parameters/extras and get them in the flex application?
Finally, I could not do this via Bundle object from native code. Passing arguments to an application must be with the <data android:scheme="my-scheme"/> tag in the manifest.
However,
One caveat is that invoking other apps with the custom URL schemes from AIR apps is not possible. The AIR security model is more restrictive and it limits schemes to: http:, https:, sms:, tel:, mailto:, file:, app:, app-storage:, vipaccess: and connectpro:. You can find more about it here and here.
From this great tutorial:
http://www.riaspace.com/2011/08/defining-custom-url-schemes-for-your-air-mobile-applications/
So far, what I have done is implement a class with member data. There, I store the data that I want to handle later (which is the same data that I wanted to pass directly via the Bundle).
public class DataModel {
//data I will get after in the actionscript side of the code
private int notificationCode;
public int getNotificationCode(){
return notificationCode;
}
public void setNotificationCode(int notificationCode){
this.notificationCode=notificationCode;
}
}
When I receive a notification in the broadcastreceiver I set the new value of the notificationCode, and then I start the activity (same as before but adding a call to setNotificationCode function).
Then, in the actionscript side, on the method onInvoke, I do the following call:
//call native functions:
//broadcastevent is the EventDispatcher that connects to the ANE
notificationCode=broadcastevent.getCode();
switch(notificationCode)
{
case Constants.DEFAULT_NOTIFICATION_CODE:
{
notificationMessage="THERE ARE NO NOTIFICATIONS";
break;
}
case Constants.UPDATE_APP_CODE:
{
notificationMessage="UPDATE APP NOTIFICATION";
break;
}
case Constants.SHOW_ALERT_CODE:
{
notificationMessage="SHOW ALERT NOTIFICATION";
break;
}
default:
break;
It is not what I exactly was looking for but I have not found other way to do something similar, and it works!

How to proactive read NFC tag - without Intent

I want to scan an NFC tag without using Intent. In other words I want to force the scan. I have already read the:
http://developer.android.com/guide/topics/connectivity/nfc/index.html
https://code.google.com/p/ndef-tools-for-android/
but both use Intents.
P.S.: My case is that the NFC tag is permanently attached to the device, so I cannot use intents.
Use foreground dispatch:
http://developer.android.com/guide/topics/connectivity/nfc/advanced-nfc.html#foreground-dispatch
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter == null || !nfcAdapter.isEnabled()) {
Log.e(TAG, "No NFC Adapter found.");
//finish();
}
Intent intent = new Intent(this, getClass());
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
//intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
try {
// Handles all MIME based dispatches. You should specify only the ones that you need.
ndef.addDataType("*/*");
} catch (IntentFilter.MalformedMimeTypeException e) {
throw new RuntimeException("failed to add MIME type", e);
}
//intentFiltersArray = new IntentFilter[]{ndef,};
//Use no intent filters to accept all MIME types
intentFiltersArray = new IntentFilter[]{};
// The tech list array can be set to null to accept all types of tag
techListsArray = new String[][]{new String[]{
IsoDep.class.getName(),
NfcA.class.getName(),
NdefFormatable.class.getName()
}};
}
public void onPause() {
nfcAdapter.disableForegroundDispatch(this);
super.onPause();
}
public void onResume() {
nfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray);
super.onResume();
}
public void onNewIntent(Intent intent) {
Tag nfcTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
//do something with tagFromIntent
}
Easy.
When you hit your "flexibility" time... and you actually change. Use INTENt, read the tag, and save the information on your sdcard, shared preferences, cloud wherever.
And use this information each time you want to read the TAG. Instead read the file which was created las ttime when tag was attached.
Next time when you will remove tag and add another one, your file will be recreated.
Don't read tag, read file created by tag attaching to device.
You cannot do what you want. Reading a tag without an intent is not possible due to the way the Android NFC subsystem is built.
Also it is a very bad idea to glue a tag onto the backside of a phone. NFC will - as long as no tag has been detected - periodically check for the existence of a tag. Once a tag has been detected it will get powered over the air until the tag does not answer anymore.
If the tag is always in range of the phone it will drain battery life like crazy.

Intercept all NFC tags on Android

I am currently trying to intercept all NFC communication on an Android Device. I have tried using a foreground dispatch will null for both the IntentFilters and TechList though when a picture is being beamed over and my application is in the foreground I do not intercept it. Things like Contacts however are intercepted.
Does anyone know how to also intercept things like pictures so that I am always grabbing all of the items sent to the phone? I do not even care about the picture, all I care about is the tag.
Thank you for all the help.
Your #enableForegroundDispatch method is like this?
protected final void enableNfcEventDiscover() {
final Intent intent = new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
final PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
if (nfcAdapter == null) {
throw new IllegalStateException(
"Cannot enable discover nfc events, you need set the NfcAdapter first");
} else {
nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);
}
}

Just read an NFC tag

I am developing an app that will use NFC tags for identification. All examples I find however, are about starting an app when a certain card is read. I tried looking for an example or documentation on how to do it differently, to no avail.
What I want is:
user starts my app
user scans NFC card
app decides next step
I got some code working now, I just don't get the tag data:
In onCreate:
pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,
getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
tech = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);
try {
tech.addDataType("*/*");
} catch (MalformedMimeTypeException e) {
throw new RuntimeException("fail", e);
}
intentFiltersArray = new IntentFilter[] { tech };
And in onResume:
nfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techList);
The intent only gets there when the app is active, but the Intent I receive is the PendingIntent I defined myself, not the ACTION_TECH_DISCOVERED intent I want.
I found part of the answer here: NFC broadcastreceiver problem
That solution doesn't provide a complete working example, so I tried a little extra. To help future visitors, I'll post my solution. It is a NfcActivity which subclasses Activity, if you subclass this NfcActivity, all you have to do is implement its NfcRead method, and you're good to go:
public abstract class NfcActivity extends Activity {
// NFC handling stuff
PendingIntent pendingIntent;
NfcAdapter nfcAdapter;
#Override
public void onResume() {
super.onResume();
pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,
getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);
}
#Override
protected void onPause() {
super.onPause();
nfcAdapter.disableForegroundDispatch(this);
}
// does nothing, has to be overridden in child classes
public abstract void NfcRead(Intent intent);
#Override
public void onNewIntent(Intent intent) {
String action = intent.getAction();
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
NfcRead(intent);
}
}
}
If you want that the user first starts your app and only then scans an NFC card, I would recommend using NFC foreground dispatch. In that way, your Activity does not need to have any intent filters in the manifest (and thus will never be called by accident when the user scans another NFC card).
With foreground dispatch enabled, your Activity can receive all NFC intents directly (without any app chooser pop-ups) and decide what to do with it (e.g. pass it on to another Activity).

Categories

Resources