Nexus SからNFC Type 3 Tag (FeliCaベースのNFCタグ)に書き込み出来た

昨日、zaki50 さんがゲットしたタグに、http://www.adamrocker.com/blog/313/nfc-write-with-nexuss.html で adamrocker さんが公開されているプログラムに、ちょこっと自分流の手を入れたもので、NDEFの書き込み成功しました。 *1

(FeliCa LiteじゃなくてFeliCaなタグです。FeliCa LiteなNFC Type 3 Tagは、Nexus Sのライブラリの問題で今の時点ではそのままではダメらしいです。) *2

ただし、実質的に手を入れたのは、タグへのコネクションを再度開く処理の部分だけなので、adamroker さんの公開されているプログラムそのままで書き込みには成功すると思います。

出来ればもっとすっきりと、mServiceHandle を直接取ってきているところなんかをやめたいんですが。いじったソースをペタリと貼っておきます。

package com.adamrocker.android.mynfc;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Set;

import android.app.Activity;
import android.content.Intent;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MyNFCActivity extends Activity implements OnClickListener {
	private Parcelable mAndroidNfcTag;
    private Integer mServiceHandle;
    private Object mTagService;
    private EditText mUrl;

	public void onClick(View v) {
	    if (v.getId() == R.id.close_btn) {
	        closeSocket();
	    } else if (v.getId() == R.id.send_btn) {
	    	try {
	    		sendSocket();
	    	} catch (Exception e) {
	    		e.printStackTrace();
	    	}
	    }
	}
	
	private void sendSocket() throws Exception {
		NfcAdapter adapter = NfcAdapter.getDefaultAdapter();
		/* -- Create Socket --
		 *  Need to know the int parameter of createLlcpConnectionlessSocket named 'sap' 
		 */ 
//		Field mServiceField = adapter.getClass().getDeclaredField("mService");
//		mServiceField.setAccessible(true);
//		Object INfcAdapter_mService = mServiceField.get(adapter);
//		Method createLlcpConnectionlessSocket = INfcAdapter_mService.getClass().getMethod("createLlcpConnectionlessSocket", Integer.TYPE);
//		Integer handle = (Integer)createLlcpConnectionlessSocket.invoke(INfcAdapter_mService, 0);
//		mServiceHandle = (Integer)createLlcpConnectionlessSocket.invoke(INfcAdapter_mService, 5);
		
		/*-- Tag Connection --*/
		Class tag = Class.forName("android.nfc.Tag");
		Method createRawTagConnection = adapter.getClass().getMethod(
				"createRawTagConnection", tag);
		Object rawTagConnection = createRawTagConnection.invoke(adapter, mAndroidNfcTag);

		/*-- check Tag connection --*/
//		Method isConnected = rawTagConnection.getClass().getMethod("isConnected");
//		Object flag = isConnected.invoke(rawTagConnection);
//		Log.i("NFC", ((Boolean)flag).toString());

		/*-- recconect Tag --*/
		Method connect = rawTagConnection.getClass().getMethod("connect");
		connect.invoke(rawTagConnection);

		/*-- check Tag connection --*/
//		Method isConnected = rawTagConnection.getClass().getMethod("isConnected");
//		Object flag = isConnected.invoke(rawTagConnection);
//		Log.i("NFC", ((Boolean)flag).toString());

//		/*-- CONNECT --*/
		/*-- get mTagService --*/
		mTagService = getDeclaredField(rawTagConnection, "mTagService");
//		Method connect = mTagService.getClass().getMethod("connect", Integer.TYPE);
//		Object flag = connect.invoke(mTagService, mServiceHandle.intValue());
			
		/*-- CREATE NdefRecord --*/
		NdefRecord[] ndefRecords = new NdefRecord[3];
		short tnf = 1;
		byte[] type0 = {0x53, 0x70};
		byte[] id0 = {};
		byte[] payload0 = {};
		ndefRecords[0] = new NdefRecord(tnf, type0, id0, payload0);
			
		tnf = 1;
		byte[] type1 = {0x55};
		byte[] id1 = {};
		byte[] payload = new byte[512];
		payload[0] = 0x00;
		String url = "http://www.google.com";
		String uris = mUrl.getText().toString();
		if (uris.length() != 0)
			url = uris;
		char[] urlc = url.toCharArray();
		for (int i = 0; i < urlc.length; i++) {
			payload[i+1] = (byte)urlc[i];
		}
		byte[] payload1 = new byte[urlc.length + 1];
		for (int i = 0; i < payload1.length; i++) {
			payload1[i] = payload[i];
		}
		ndefRecords[1] = new NdefRecord(tnf, type1, id1, payload1);
			
		tnf = 1;
		byte[] type2 = {0x61, 0x63, 0x74};
		byte[] id2 = {};
		byte[] payload2 = {0x02};
		ndefRecords[2] = new NdefRecord(tnf, type2, id2, payload2);
			
		/*-- CREATE NdefMessage --*/
		NdefMessage ndefMsg = null;
		if (true) {
		    ndefMsg = new NdefMessage(ndefRecords);
		} else {//TEST
		    //google.com
		    byte[] data = {(byte)0xD1,0x2,0x16,0x53,0x70,(byte)0x91,0x1,0xB,0x55,0x1,0x67,0x6F,0x6F,0x67,0x6C,0x65,0x2E,0x63,0x6F,0x6D,0x51,0x3,0x1,0x61,0x63,0x74,0x0};
		    ndefMsg = new NdefMessage(data);
		}
			
		/*-- WRITE --*/
		Method write = mTagService.getClass().getMethod("write", Integer.TYPE, NdefMessage.class);
		Object retValue = write.invoke(mTagService, mServiceHandle, ndefMsg);
		Log.i("NFC", ((Integer)retValue).toString());
	}
	
	private void closeSocket() {
	    if (mTagService != null) {
	        try {
	            Method INfcTag_close = mTagService.getClass().getMethod("close", Integer.TYPE);
	            INfcTag_close.invoke(mTagService, mServiceHandle);
	        } catch (Exception e){
	            e.printStackTrace();
	        }
	    }
	}

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		TextView tv = (TextView) findViewById(R.id.result_tv);
		resolveIntent(getIntent(), tv);
		((Button) findViewById(R.id.send_btn)).setOnClickListener(this);
		((Button) findViewById(R.id.close_btn)).setOnClickListener(this);
		mUrl = (EditText)findViewById(R.id.url_et);
	}
	
	public void onStop() {
	    super.onStop();
	    closeSocket();
	}

	private void resolveIntent(Intent it, TextView tv) {
		String action = it.getAction();
		Bundle b = it.getExtras();

		Set<String> ks = b.keySet();
		int count = 0;
		for (String s : ks) {
			Log.i("MyNFC", "KEY[" + count++ + "]=" + s);
		}
		mAndroidNfcTag = it.getParcelableExtra("android.nfc.extra.TAG");
		try {
			dumpTagData(mAndroidNfcTag, tv);
		} catch (Exception e) {
			e.printStackTrace();
		}
		Log.i("MyNFC", it.toUri(-1));
		if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
			Parcelable[] rawMsgs = it
					.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
			NdefMessage[] msgs;
			if (rawMsgs != null) {
				msgs = new NdefMessage[rawMsgs.length];
				for (int i = 0; i < rawMsgs.length; i++) {
					msgs[i] = (NdefMessage) rawMsgs[i];
				}
			}
		}
	}

	private void dumpTagData(Parcelable p, TextView tv) throws SecurityException,
			IllegalArgumentException,
			IllegalAccessException {
		StringBuilder sb = new StringBuilder();
		String name = p.getClass().getCanonicalName();
		LOG("ClassName", name);
		Field f = null;
		Class tag = p.getClass();
		try {
			//class android.nfc.Tag
		    f = tag.getDeclaredField("mIsNdef");
		} catch (Exception e) {
		    //class android.nfc.NdefTag
		    tag = tag.getSuperclass();
		    try {
		        f = tag.getDeclaredField("mIsNdef");
		    } catch (Exception e1) {
		        e1.printStackTrace();
		    }
		}
		f.setAccessible(true);
		Boolean mIsNdef = (Boolean) f.get(p);
		LOG("mIsNdef", mIsNdef.toString());
		sb.append("mIsNdef:").append(mIsNdef.toString()).append("\n");
		
		try {
			//class android.nfc.Tag
		    f = tag.getDeclaredField("mId");
		} catch (Exception e) {
			//class android.nfc.NdefTag
		    tag = tag.getSuperclass();
		    try {
		        f = tag.getDeclaredField("mId");
		    } catch (Exception e1) {
		        e1.printStackTrace();
		    }
		}
		f.setAccessible(true);
		byte[] mId = (byte[]) f.get(p);
		sb.append("mId:").append(getHex(mId)).append("\n");
 
		try {
			//class android.nfc.Tag
		    f = tag.getDeclaredField("mRawTargets");
		} catch (Exception e) {
		    //class android.nfc.NdefTag
		    tag = tag.getSuperclass();
		    try { 
		        f = tag.getDeclaredField("mRawTargets");
		    } catch (Exception e1) {
		        e1.printStackTrace();
		    }
		}
		f.setAccessible(true);
		String[] mRawTargets = (String[]) f.get(p);
		sb.append("mRawTargets:");
		for(String s : mRawTargets) {
			sb.append(s).append(".");
		}
		sb.append("\n");

		try {
			//class android.nfc.Tag
		    f = tag.getDeclaredField("mPollBytes");
	    } catch (Exception e) {
		    //class android.nfc.NdefTag
		    tag = tag.getSuperclass();
		    try { 
		        f = tag.getDeclaredField("mPollBytes");
		    } catch (Exception e1) {
		        e1.printStackTrace();
		    }
		}
	    f.setAccessible(true);
		byte[] mPollBytes = (byte[]) f.get(p);
		sb.append("mPollBytes:").append(getHex(mPollBytes)).append("\n");

		try {
			//class android.nfc.Tag
		    f = tag.getDeclaredField("mActivationBytes");
		} catch (Exception e) {
		    //class android.nfc.NdefTag
		    tag = tag.getSuperclass();
		    try { 
		        f = tag.getDeclaredField("mActivationBytes");
		    } catch (Exception e1) {
		        e1.printStackTrace();
		    }
		}
		f.setAccessible(true);
		byte[] mActivationBytes = (byte[]) f.get(p);
		String ACTIV = getHex(mActivationBytes);
		sb.append("mActivationBytes:").append(ACTIV).append("\n");

		try {
			//class android.nfc.Tag
			f = tag.getDeclaredField("mServiceHandle");
		} catch (Exception e) {
		    //class android.nfc.NdefTag
		    tag = tag.getSuperclass();
		    try { 
		        f = tag.getDeclaredField("mActivationBytes");
		    } catch (Exception e1) {
		        e1.printStackTrace();
		    }
		}
		f.setAccessible(true);
		mServiceHandle = (Integer) f.get(p);
		sb.append("mServiceHandle:").append(mServiceHandle.intValue()).append("\n");

		tv.setText(sb.toString());
	}

	private String getHex(byte[] bs) {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < bs.length; i++) {
			int b = new Byte(bs[i]).intValue();
			sb.append(Integer.toHexString(b).toString());
			if (i != bs.length - 1) {
				sb.append(".");
			}
		}
		return sb.toString();
	}

	private Object getDeclaredField(Object obj, String name)
			throws SecurityException, NoSuchFieldException,
			IllegalArgumentException, IllegalAccessException {
		Field f = obj.getClass().getDeclaredField(name);
		f.setAccessible(true);
		return f.get(obj);
	}

	private void dumpField(Object obj) {
		Field[] fs = obj.getClass().getDeclaredFields();
		int i = 0;
		for (Field f : fs) {
			DUMP(obj.getClass().getName() + "'s Fields[" + i++ + "] = "
					+ f.getName());
		}
	}

	private void dumpMethod(Object obj) {
		Method[] ms = obj.getClass().getDeclaredMethods();
		int i = 0;
		for (Method m : ms) {
			DUMP(obj.getClass().getName() + "'s Meyhodes[" + i++ + "]"
					+ m.toString());
		}
	}

	private void LOG(String key, String val) {
		DUMP(key + " = " + val);
	}

	private void DUMP(String val) {
		Log.i("MyNFC", val);
	}
}

*1:ただし、ちょっとデータフォーマットの問題があるのかゴミUnknown tag typeが混入しているようです。

*2:http://goo.gl/NTP1B のパッチを適用することでFeliCa LiteもOKになりました。@Wataloot さん、ありがとうございます!