Issue
I have a question about NFC and Pending Intent function. I am building an app, which reads content of NDEF Tag and send value to server. After much searching I found a bug in the code, specifically in a function PendingIntent.
This is code of MainActivity.java. When I change FLAG_MUTABLE to IMMUTABLE, value of intent is null and app crash. When it is MUTABLE, value of intent is android.nfc.action.NDEF_DISCOVERED and everything works. I want to know, why it is, because every example and even somewhere in docs I found, that it should be IMMUTABLE.
import androidx.appcompat.app.AppCompatActivity;
import android.app.PendingIntent;
import android.content.Intent;
import android.nfc.FormatException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.Ndef;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.widget.Toast;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
public class MainActivity extends AppCompatActivity {
private NfcAdapter nfcAdapter;
private PendingIntent pendingIntent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter == null) {
Toast.makeText(this, "Nepodporovaný telefon", Toast.LENGTH_LONG).show();
} else if (!nfcAdapter.isEnabled()) {
Toast.makeText(this, "NFC je vypnuté", Toast.LENGTH_LONG).show();
startActivity(new Intent(Settings.ACTION_NFC_SETTINGS));
}
pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), PendingIntent.FLAG_MUTABLE);
}
@Override
protected void onResume() {
super.onResume();
if (nfcAdapter != null) {
nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);
}
}
@Override
protected void onPause() {
super.onPause();
if (nfcAdapter != null) {
nfcAdapter.disableForegroundDispatch(this);
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.d("INTENT ACTION", intent.getAction());
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (tag != null) {
Ndef ndef = Ndef.get(tag);
if (ndef == null) {
Toast.makeText(this, "Prázdný NFC Tag", Toast.LENGTH_SHORT).show();
return;
}
try {
ndef.connect();
NdefMessage ndefMessage = ndef.getNdefMessage();
if (ndefMessage != null) {
NdefRecord[] records = ndefMessage.getRecords();
for (NdefRecord record : records) {
if (record.getTnf() == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(record.getType(), NdefRecord.RTD_TEXT)) {
String text = readText(record);
// TODO: Odeslat data do Google Sheets
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
}
}
}
} catch (IOException | FormatException e) {
e.printStackTrace();
} finally {
try {
ndef.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} else {
Toast.makeText(this, "Poškozený NFC Tag", Toast.LENGTH_LONG).show();
}
}
private String readText(NdefRecord record) throws UnsupportedEncodingException {
byte[] payload = record.getPayload();
String textEncoding = ((payload[0] & 128) == 0) ? "UTF-8" : "UTF-16";
int languageCodeLength = payload[0] & 63;
return new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding);
}
}
Manifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.NFC"/>
<uses-feature android:name="android.hardware.nfc" android:required="true"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.APPNFC"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Solution
FLAG_IMMUTABLE : Flag indicating that the created PendingIntent should be immutable. This means that the additional intent argument passed to the send methods to fill in unpopulated properties of this intent will be ignored.
FLAG_IMMUTABLE only limits the ability to alter the semantics of the intent that is sent by send() by the invoker of send(). The creator of the PendingIntent can always update the PendingIntent itself via FLAG_UPDATE_CURRENT.
As per Android Documentation, FLAG_IMMUTABLE removes the ability of altering the semantics, as here the the info of NFC tag is not send with the intent.
But for FLAG_MUTABLE,
FLAG_MUTABLE should only be used when some functionality relies on modifying the underlying intent, e.g. any PendingIntent that needs to be used with inline reply or bubbles.
Mutable Flag allows to modify.
That could be the reason for working with Mutable and Not working with Immutable. That's' why when you set the PendingIntent flag to IMMUTABLE, your app crashes because the NFC intent cannot be added to your PendingIntent, leading to a null intent value in your onNewIntent() method.
Answered By - K M Rejowan Ahmmed
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.