There are many samples to read IDm, which is the unique ID of the FeliCa card, but there are not many samples to read information on various electronic money, so I will read the information on the WAON card I had (WAON number for the time being).
WAON cards are sold at Ministop for 300 yen.
It looks like the following.
It looks like the following.
The general flow from recognizing and reading the card is as follows.
To use the NFC function, add the following to AndroidManifext.xml.
<uses-permission android:name="android.permission.NFC" />
In the first place, it is also necessary to turn on the Reader / Writer function of the terminal itself.
I will try to implement it roughly.
MainActivity.java
This time, I'm using Reader Mode because I want to turn on / off the reading function with a button. The enable / disable is controlled by the start button and stop button, respectively.
When the card (Tag) is recognized, onTagDiscovered () of Callback class is called, so perform the necessary processing in it. Use transcive () to execute the FeliCa command. This time I'm running polling and readWithoutEncryption.
Polling is indispensable to identify the card ID (IDm) of the communication destination. In readWithoutEncryption, you need to specify the "location" to read in the service code. This time, I want to read the WAON number (card number), so I specify 0x68 and 0x4f.
The service code has not been published, but is inspected by volunteers.
If you want to use WAON's other services (such as balance) or information on other cards, get the service code and format.
In addition, FeliCa has a keyed area and a keyless area, and the area such as charge is held in the "keyed area", so it cannot be operated by a third party. Just to be sure.
MainActivity.java
package jp.bluecode.waon_java;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.NfcF;
import android.os.Handler;
import android.os.Looper;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Formatter;
public class MainActivity extends AppCompatActivity {
//Widget declaration
TextView txt_idm;
TextView txt_pmm;
TextView txt_waonno;
Button btn_start;
Button btn_stop;
//Declaration of NfcAdapter
NfcAdapter nfcAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Widget initialization
txt_idm = findViewById(R.id.txt_idm);
txt_pmm = findViewById(R.id.txt_pmm);
txt_waonno = findViewById(R.id.txt_waonno);
btn_start = findViewById(R.id.btn_start);
btn_stop = findViewById(R.id.btn_stop);
//Initial setting (toggle)
btn_stop.setEnabled(false);
//Initialization of NfcAdapter
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
//onClick event settings
btn_start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//Toggle settings
btn_start.setEnabled(false);
btn_stop.setEnabled(true);
//TextView initialization
txt_idm.setText("");
txt_pmm.setText("");
txt_waonno.setText("");
//ReaderMode On
nfcAdapter.enableReaderMode(MainActivity.this,new MyReaderCallback(),NfcAdapter.FLAG_READER_NFC_F,null);
}
});
btn_stop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//Toggle settings
btn_start.setEnabled(true);
btn_stop.setEnabled(false);
//ReaderMode Off
nfcAdapter.disableReaderMode(MainActivity.this);
}
});
}
//Inner class description for Callback
private class MyReaderCallback implements NfcAdapter.ReaderCallback{
@Override
public void onTagDiscovered(Tag tag){
//If the tag is found, log output for the time being
Log.d("Hoge","Tag Discovered!");
//Initialize NfcF to communicate with FeliCa
NfcF nfc = NfcF.get(tag);
try{
nfc.connect();
//For the time being, it is controlled by various FeliCa raw commands. tag for idm.getId()You can get it.
//polling command (FeliCa raw command) Specify the system code 0xFE 0x00 for the common area.
byte[] polling_request = {(byte)0x06,(byte)0x00,(byte)0xFE,(byte)0x00,(byte)0x00,(byte)0x00};
//Byte array for receiving response
//Command transmission / reception
byte[] polling_response = nfc.transceive(polling_request);
//Extraction of idm
byte[] idm = Arrays.copyOfRange(polling_response,2,10);
//Taking out pmm (in addition)
byte[] pmm = Arrays.copyOfRange(polling_response,11,19);
//Convert byte string to string
final String idmString = bytesToHexString(idm);
final String pmmString = bytesToHexString(pmm);
//waonno processing
//waonnno request
//Assemble the request command using a custom function
byte[] waonno_request = readWithoutEncryption(idm,2);
//Command transmission / reception
byte[] wannno_response = nfc.transceive(waonno_request);
//Cut out the WAON number part
byte[] waonno = Arrays.copyOfRange(wannno_response,13,21);
//String conversion
final String waonnoString = bytesToHexString(waonno);
//UI update for parent thread
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(new Runnable() {
@Override
public void run() {
txt_idm.setText(idmString);
txt_pmm.setText(pmmString);
txt_waonno.setText(waonnoString);
}
});
nfc.close();
}catch(Exception e){
Log.e("Hoge",e.getMessage());
}
}
}
//Non-encryption area read command (WAON number area specialization)
private byte[] readWithoutEncryption(byte[] idm, int blocksize) throws IOException {
ByteArrayOutputStream bout = new ByteArrayOutputStream(100); //For the time being
//readWithoutEncryption command assembly
bout.write(0); //Command length (enter later)
bout.write(0x06); //0x06 stands for Read Without Encryption
bout.write(idm); //8byte:idm
bout.write(1); //Number of services
bout.write(0x4f); //Service code list WAON card number is 684F
bout.write(0x68); //Service code list
bout.write(blocksize); //Number of blocks
for(int i=0; i<blocksize; i++){
bout.write(0x80); //Block list
bout.write(i);
}
byte[] msg = bout.toByteArray();
msg[0] = (byte)msg.length;
return msg;
}
//Function for converting bytes to hexadecimal string
private String bytesToHexString(byte[] bytes){
StringBuilder sb = new StringBuilder();
Formatter formatter = new Formatter(sb);
for(byte b: bytes){
formatter.format("%02x",b);
}
//Capitalize back (just adjust the appearance)
return sb.toString().toUpperCase();
}
}
that's all.
activity_main.xml
Activity_main.xml is also described for reference, but you should lay it out as you like.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/lbl_top"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="WAON number Viewer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/lbl_idm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="68dp"
android:layout_marginTop="32dp"
android:text="IDm:"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/lbl_top" />
<TextView
android:id="@+id/txt_idm"
android:layout_width="180dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="32dp"
android:background="#E8EAF6"
app:layout_constraintStart_toEndOf="@+id/lbl_idm"
app:layout_constraintTop_toBottomOf="@+id/lbl_top" />
<TextView
android:id="@+id/lbl_pmm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="60dp"
android:layout_marginTop="24dp"
android:text="PMm:"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/lbl_idm" />
<TextView
android:id="@+id/txt_pmm"
android:layout_width="180dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:background="#E8EAF6"
app:layout_constraintStart_toEndOf="@+id/lbl_pmm"
app:layout_constraintTop_toBottomOf="@+id/txt_idm" />
<TextView
android:id="@+id/lbl_wanno"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:text="WAON number:"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/lbl_pmm" />
<TextView
android:id="@+id/txt_waonno"
android:layout_width="180dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:background="#F3E5F5"
app:layout_constraintStart_toEndOf="@+id/lbl_wanno"
app:layout_constraintTop_toBottomOf="@+id/txt_pmm" />
<Button
android:id="@+id/btn_start"
android:layout_width="160dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="Start Polling"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/txt_waonno" />
<Button
android:id="@+id/btn_stop"
android:layout_width="160dp"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Stop Polling"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_start" />
</android.support.constraint.ConstraintLayout>
AndroidManifest.xml
This is also for reference only. I'm only editing the NFC permissions mentioned at the beginning.
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="jp.bluecode.waon_java">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.NFC" />
</manifest>