Home » C++ » How to get NFC working on Android using Qt 5.6

How to get NFC working on Android using Qt 5.6

Posted by: admin November 30, 2017 Leave a comment

Questions:

I’m trying to read NFC tags on my Android phone using the NFC module of Qt.

According to this page, Qt will support NFC on Android starting from version 5.6. This version hasn’t been released yet, so I built it from source, following the instructions on this page, and installed it in Qt creator.

First step is to get the tag/card detection working and I’m stuck there. My test application instantiates a QNearFieldManager, checks if NFC is available and connects slots to the signals targetDetected and targetLost.
The QNearFieldManager::isAvailable method reports that NFC is available (with Qt 5.5 it did not), but the signals targetDetected/targetLost are never fired.

Below is the code of my test application:

#include <QLabel>
#include <QVBoxLayout>

#include <QNearFieldManager>
#include <QNearFieldTarget>

#include <QDebug>

#include "window.h"

Window::Window(QWidget *parent)
: QWidget(parent)
{
    nfcLabel_ = new QLabel(this);

    QVBoxLayout *mainLayout = new QVBoxLayout;
    mainLayout->addWidget(nfcLabel_, 1);

    setLayout(mainLayout);

    setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));

    setWindowTitle(tr("NFC Test"));

    nfc_ = new QNearFieldManager(this);
    if (nfc_->isAvailable()) {
        nfcLabel_->setText("NFC available");
    } else {
        nfcLabel_->setText("NFC not available");
        qWarning() << "NFC not available";
    }

    nfc_->setTargetAccessModes(QNearFieldManager::NdefReadTargetAccess); // doesn't help

    nfc_->registerNdefMessageHandler(this, SLOT(handleNdefMessage(QNdefMessage,QNearFieldTarget*))); // doesn't help

    connect(nfc_, SIGNAL(targetDetected(QNearFieldTarget*)), this, SLOT(targetDetected(QNearFieldTarget*)));
    connect(nfc_, SIGNAL(targetLost(QNearFieldTarget*)), this, SLOT(targetLost(QNearFieldTarget*)));

    if (!nfc_->startTargetDetection()) {
        qWarning() << "NFC target detection could not be started";
    }
}

Window::~Window()
{
    nfc_->stopTargetDetection();
}

void Window::targetDetected(QNearFieldTarget * /*target*/)
{
    nfcLabel_->setText("Target detected");
}

void Window::targetLost(QNearFieldTarget *target)
{
    nfcLabel_->setText("Target lost");
    target->deleteLater();
}

void Window::handleNdefMessage(const QNdefMessage &/*message*/, QNearFieldTarget */*target*/)
{
    qDebug() << "Ndef Message";
}

I must be missing something…

UPDATE 1

It appears that the AndroidManifest.xml file needs to be modified. I tried different things, but none seem to produce the desired effect. I can only get the targetDetected and targetLost events to fire when the manifest defines an intent-filter like this:

<intent-filter>
    <action android:name="android.nfc.action.TAG_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
</intent-filter>

However, this also causes the app to be started each time a target is scanned, even if the app is already running. What I need is to start the app and then wait for a target to be scanned. How can I accomplish this?

UPDATE 2

Below is the full AndroidManifest.xml file that I tried.

<?xml version="1.0"?>
<manifest package="org.qtproject.example" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" android:versionCode="1" android:installLocation="auto">
    <application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="-- %%INSERT_APP_NAME%% --" android:theme="@android:style/Theme.Holo">
    <activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="-- %%INSERT_APP_NAME%% --" android:screenOrientation="unspecified" android:launchMode="singleTop">
        <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
        </intent-filter>

        <!-- Without this, the targetDetected/targetLost signals aren't fired -->
        <intent-filter>
        <action android:name="android.nfc.action.TAG_DISCOVERED"/>
        <category android:name="android.intent.category.DEFAULT"/>
        </intent-filter>

        <meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
        <meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
        <meta-data android:name="android.app.repository" android:value="default"/>
        <meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
        <meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
        <!-- Deploy Qt libs as part of package -->
        <meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
        <meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
        <meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
        <!-- Run with local libs -->
        <meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
        <meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
        <meta-data android:name="android.app.load_local_libs" android:value="-- %%INSERT_LOCAL_LIBS%% --"/>
        <meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
        <meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
        <!--  Messages maps -->
        <meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
        <meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
        <meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
        <!--  Messages maps -->

        <!-- Splash screen -->
        <!--
        <meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/>
        -->
        <!-- Splash screen -->

        <!-- Background running -->
        <!-- Warning: changing this value to true may cause unexpected crashes if the
              application still try to draw after
              "applicationStateChanged(Qt::ApplicationSuspended)"
              signal is sent! -->
        <meta-data android:name="android.app.background_running" android:value="false"/>
        <!-- Background running -->
    </activity>
    </application>
    <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="14"/>
    <supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
    <uses-feature android:name="android.hardware.nfc" android:required="true"/>
    <uses-permission android:name="android.permission.NFC"/>
</manifest>
Answers:

If you are using NFC tag of certain manufacturer the same should be present in the mobile NFC also then only it will pair correctly as of now NFC do not support globally. For eg. if the NFC Present inside the Sony device will max support its manufacture only and in most cases it fails to connect to others devices like nexus. So try to find your Manufacturer and connect it . Hope it helps you..

Questions:
Answers:

I don’t believe you want those intent filters in your manifest. Adding those, tells the operating system to start your app when a tag is detected (which is why it is doing so). It does look like you’re registering correctly in your code for NFC events, so perhaps the issue is the brand of NFC chip in your phone, in conjunction with the tag you’re using to test with. If your phone is equipped with a Broadcom NFC chip, and you’re trying to use NXP’s Mifare Classic tag, you’ll run into issues. Using a Desfire, or NTAG tag might help.

Questions:
Answers:

I have solved this one.

The reason is that in QtNfc.java where qt handles NFC intents it handles only NDEF tags by filtering ACTION_NDEF_DISCOVERED actions (and ACTION_TECH_DISCOVERED for NDEF tags that will report as tech) without simple ACTION_TAG_DISCOVERED (despite the fact it handles it in getStartIntent fuction).

But I supposed you just want to scan a simple tag to read uid, as I do. So you need to add ACTION_TAG_DISCOVERED to filter list in QtNfc.java start() function:

IntentFilter[] filters = new IntentFilter[3];
filters[0] = new IntentFilter();
filters[0].addAction(NfcAdapter.ACTION_TAG_DISCOVERED);
filters[0].addCategory(Intent.CATEGORY_DEFAULT);
...

I think it would be more correct to modify filter to ACTION_TAG_DISCOVERED in setContext too.
The fastest way is to open in qt creator qtconnectivity .pro for corresponding branch, correct QtNfc.java, build it and replace libQt5Nfc.so in android_armv7\lib qt folder (QtNfc.jar and QtNfc-bundled.jar in android_armv7\jar folder will be updated during build).

That is. No need to modify manifest in working application.

By the way this one:

<uses-permission android:name="android.permission.NFC"/>

qt add automatically when you add module nfc to .pro

This one

<uses-feature android:name="android.hardware.nfc" android:required="true"/>

is not necessary I suppose. It works without it.

But you can add this intent-filter if you want to tell android to start your app when a tag is detected as Anansi mentioned upper. But I really recommend to add
android:alwaysRetainTaskState=”true” android:launchMode=”singleInstance”
in application activity (like here).

I test all this with android 4.4.4 tablet and ndefeditor example. It fires targetDetected/targetLost perfectly. There can be another default application for tags in system (for example NFC Reader) and it opens up on every tag detecting, but not the time ndefeditor is waiting tag (button retrieve). And of course qt example says “NDEF read error” for non-NDEF tags, but it detects them and reads uid. Precisely what I needed.

I add the suggestion to Qt Jira and submit the patch.

The only thing I didn’t understand – why ndefeditor had worked on another tablet with android 4.2. Maybe it is a hardware aspect and android on another tablet was always intent ACTION_NDEF_DISCOVERED?

Questions:
Answers:

Hello below is the answer, let me know if you are looking for this only. 🙂
Firstly write this in onCreate()

//Code in onCreate
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
        mPendingIntent = PendingIntent.getActivity(this, 0,
                new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);

        // set an intent filter for all MIME data
        IntentFilter ndefIntent = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
        try {
            ndefIntent.addDataType("*/*");
            mIntentFilters = new IntentFilter[] { ndefIntent };
        } catch (Exception e) {
            Log.fnLogToFile(strFunctionName + "-" + e.getMessage(), ErrorType.ERROR);
            Log.createCrashReport();
        }

        mNFCTechLists = new String[][] { new String[] { NfcF.class.getName() } };

Write this onNewIntent outside onCreate()

@Override
    public void onNewIntent(Intent intent) {        

        StackTraceElement[] arrFunctionName = Thread.currentThread().getStackTrace() ;
        String strFunctionName = arrFunctionName[arrFunctionName.length-1].getMethodName();
        Log.fnLogToFile(strFunctionName + "Entered", ErrorType.INFO);
        tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);

        String strTagData = "";
        // parse through all NDEF messages and their records and pick text type only
        Parcelable[] data = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);

        if (data != null) {
            try {
                for (int i = 0; i < data.length; i++) {                 
                    NdefRecord [] recs = ((NdefMessage)data[i]).getRecords();
                    for (int j = 0; j < recs.length; j++) {
                        if (recs[j].getTnf() == NdefRecord.TNF_WELL_KNOWN &&
                                Arrays.equals(recs[j].getType(), NdefRecord.RTD_TEXT)) {

                            byte[] payload = recs[j].getPayload();
                            String textEncoding = ((payload[0] & 0200) == 0) ? "UTF-8" : "UTF-16";
                            int langCodeLen = payload[0] & 0077;
                            //tag data is saved in strTagData
                            strTagData += ("\n" +
                                    new String(payload, langCodeLen + 1,
                                            payload.length - langCodeLen - 1, textEncoding));
                        }
                    }
                }
            } catch (Exception e) {
                Log.fnLogToFile(strFunctionName + "-" + e.getMessage(), ErrorType.ERROR);
                Log.createCrashReport();
                Log.e("TagDispatch", e.toString());
            }

        }
    }

You will get NFC data in strTagData variable

Permission in Manifest