Search code examples
androidnfcmifare

Attempt to invoke virtual method 'void android.nfc.tech.MifareClassic.connect()' on a null object reference


I've been developing an application in Android Studio to read and write NFC tags, specifically Mifare Classic tags. I managed to develop and test it on my smartphone (with S.O. KitKat) in early 2016 (a year ago).

As I mentioned, leave aside the application, and after having updated the version of Android Studio, the SDK and the S.O. From my smartphone to MarshMallow, this error appears when trying to write to the label: "java.lang.NullPointerException: Attempt to invoke virtual method 'void android.nfc.tech.MifareClassic.connect()' on a null object reference".

This error is apparently generated when trying to connect to MifareClassic tag.

Attached the code of my activity replacing some parts with ... that I consider are irrelevant.

import android.annotation.SuppressLint;
import android.app.Activity;

import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.nfc.FormatException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.MifareClassic;
import android.nfc.tech.Ndef;
import android.nfc.tech.NdefFormatable;
import android.os.Bundle;
import android.view.View.OnClickListener;
...

import java.io.IOException;
import java.io.UnsupportedEncodingException;

@SuppressLint("Escribir")
public class escribir extends Activity {
    NfcAdapter adapter;
    PendingIntent pendingIntent;
    IntentFilter writeTagFilters[];
    boolean writeMode;
    Tag myTag;
    MifareClassic mfc;
    Context context;
    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_datospropietario);
        context = this;
        ...

        Button btnWrite = (Button)findViewById(R.id.button);
        final String b = getIntent().getExtras().getString("datos");

        btnWrite.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {

                final String mensaje = (b + ...);

                if (first.getText().toString().isEmpty()) {
                    Toast.makeText(context, context.getString(R.string.missing_fields), Toast.LENGTH_SHORT).show();
                } else {
                    if (myTag == null) {
                        Toast.makeText(context, context.getString(R.string.error_notag), Toast.LENGTH_LONG).show();
                    } else {
                    MifareClassic tmpMFC = null;
                    try {
                        tmpMFC = MifareClassic.get(myTag);
                    } catch (Exception e) {
                        Toast.makeText(context, context.getString(R.string.error_notag), Toast.LENGTH_LONG).show();
                        e.printStackTrace();
                    }
                    mfc = tmpMFC;

                    int sect;
                    if (mfc != null) {
                        sect = mfc.getSectorCount();
                    }
                    try {
                        mfc.connect();
                        ...
                    } catch (IOException e) {
                        Toast.makeText(context, context.getString(R.string.error_notag), Toast.LENGTH_LONG).show();
                        e.printStackTrace();
                        myTag = null; 
                    }
                }
            }
        });

        adapter = NfcAdapter.getDefaultAdapter(this);
        pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
        IntentFilter tagDetected = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
        tagDetected.addCategory(Intent.CATEGORY_DEFAULT);
        writeTagFilters = new IntentFilter[]{tagDetected};
    }

    private void write(String text, Tag tag, int sector) throws IOException, FormatException {

        NdefRecord[] records = {createRecord(text), NdefRecord.createApplicationRecord("my_app")};
        NdefMessage mensaje = new NdefMessage(records);

        NdefFormatable formatable = NdefFormatable.get(tag);

        if (formatable != null) {
            formatable.connect();
            formatable.format(mensaje);
            formatable.close();
        } else {
            Ndef ndef = Ndef.get(tag);
            ndef.connect();
            ndef.writeNdefMessage(mensaje);
            ndef.close();
        }

        MifareClassic mfc = MifareClassic.get(tag);
        ...
    }

    @SuppressLint("Escribir") private NdefRecord createRecord(String text) throws UnsupportedEncodingException{
        String lang = "es";
        byte[] textBytes = text.getBytes();
        byte[] langBytes = lang.getBytes("US-ASCII");
        int langLength = langBytes.length;
        int textLength = textBytes.length;
        byte[] payLoad = new byte[1 + langLength + textLength];

        payLoad[0] = (byte) langLength;

        System.arraycopy(langBytes, 0, payLoad, 1, langLength);
        System.arraycopy(textBytes, 0, payLoad, 1 + langLength, textLength);

        return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], payLoad);
    }

    @SuppressLint("Escribir") protected void onNewIntent(Intent intent){
        if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
            myTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
        }
    }

    public void onPause(){
        super.onPause();
        WriteModeOff();
    }
    public void onResume(){
        super.onResume();
        WriteModeOn();
    }

    @SuppressLint("Escribir") private void WriteModeOn(){
        writeMode = true;
        adapter.enableForegroundDispatch(this, pendingIntent, writeTagFilters, null);
    }

    @SuppressLint("Escribir") private void WriteModeOff(){
        writeMode = false;
        adapter.disableForegroundDispatch(this);
    }
}

Solution

  • After researching the internet about this problem, I discovered that writing problems on NFC tags were presented on various devices, such as some HTC or Sony Xperia models. These issues were presented after upgrading the Android version to SDK 5.1 (Lollipop).

    This problem originated because some of the manufacturers modified the stack order where the different Tag types were found causing conflicts when requesting TechExtras and returning SAK null or wrong values.

    The detailed explanation I found for this is as follows:

    HTC One: It seems, the reason for this bug is TechExtras of NfcA is null. However, TechList contains MifareClassic.

    Sony Xperia Z3 (+ emmulated MIFARE Classic tag): The buggy tag has two NfcA in the TechList with different SAK values and a MifareClassic (with the Extra of the second NfcA). Both, the second NfcA and the MifareClassic technique, have a SAK of 0x20. According to NXP's guidelines on identifying MIFARE tags (Page 11), this a MIFARE Plus or MIFARE DESFire tag. This method creates a new extra with the SAK values of both NfcA occurrences ORed (as mentioned in NXP's MIFARE type identification procedure guide) and replace the Extra of the first NfcA with the new one.

    For more information please refer to https://github.com/ikarus23/MifareClassicTool/issues/52

    And the patch proposed by the bildin user, who fixed my problem:

    public Tag patchTag(Tag oTag)
    {
        if (oTag == null) 
            return null;
    
        String[] sTechList = oTag.getTechList();
    
        Parcel oParcel, nParcel;
    
        oParcel = Parcel.obtain();
        oTag.writeToParcel(oParcel, 0);
        oParcel.setDataPosition(0);
    
        int len = oParcel.readInt();
        byte[] id = null;
        if (len >= 0)
        {
            id = new byte[len];
            oParcel.readByteArray(id);
        }
        int[] oTechList = new int[oParcel.readInt()];
        oParcel.readIntArray(oTechList);
        Bundle[] oTechExtras = oParcel.createTypedArray(Bundle.CREATOR);
        int serviceHandle = oParcel.readInt();
        int isMock = oParcel.readInt();
        IBinder tagService;
        if (isMock == 0)
        {
            tagService = oParcel.readStrongBinder();
        }
        else
        {
            tagService = null;
        }
        oParcel.recycle();
    
        int nfca_idx=-1;
        int mc_idx=-1;
    
        for(int idx = 0; idx < sTechList.length; idx++)
        {
            if(sTechList[idx] == NfcA.class.getName())
            {
                nfca_idx = idx;
            }
            else if(sTechList[idx] == MifareClassic.class.getName())
            {
                mc_idx = idx;
            }
        }
    
        if(nfca_idx>=0&&mc_idx>=0&&oTechExtras[mc_idx]==null)
        {
            oTechExtras[mc_idx] = oTechExtras[nfca_idx];
        }
        else
        {
            return oTag;
        }
    
        nParcel = Parcel.obtain();
        nParcel.writeInt(id.length);
        nParcel.writeByteArray(id);
        nParcel.writeInt(oTechList.length);
        nParcel.writeIntArray(oTechList);
        nParcel.writeTypedArray(oTechExtras,0);
        nParcel.writeInt(serviceHandle);
        nParcel.writeInt(isMock);
        if(isMock==0)
        {
            nParcel.writeStrongBinder(tagService);
        }
        nParcel.setDataPosition(0);
    
        Tag nTag = Tag.CREATOR.createFromParcel(nParcel);
    
        nParcel.recycle();
    
        return nTag;
    }
    

    This patch was provided by bildin (https://github.com/bildin).

    Although the device on which I tested was not of the earlier brands, the patch worked perfectly on my Moto X (1st Gen), with a modified ROM Marshmallow, so I guess it will also work for a wide range of devices with The NXP chip PN544