[JAVA] Read and see the information on the FeliCa (WAON) card

Thing you want to do

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.

Premise

specification

It looks like the following.

It looks like the following.

スクリーンショット 2019-05-31 9.07.07.png

Process flow

The general flow from recognizing and reading the card is as follows.

Preparation

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.

Implementation

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>

Recommended Posts

Read and see the information on the FeliCa (WAON) card
Read the IC balance of your student ID card (Felica) on Android
Compile and run Java on the command line