非接触ICカードでのカードとリーダライタの関係

スライドで触れたのですが、日本のおサイフケータイがカードモードで、NFCNexus Sがリーダライタになっていることって何が問題なのか、もう少し補足しておきましょう。

まず、非接触ICカードの仕組みってのがこんな感じです。

リーダライタから搬送波(電波)が飛んで、カードはそれから起電力を得て動作します。リーダライタはカードが圏内に入ったことを認識して、コマンドをカードに送り、カードはそのコマンドに対応した処理を行って、レスポンスを返します。

おサイフケータイはこんな感じです。

搬送波からの電力では動作していないかも。多少は使ってると思いますが端末の電力が主体で動作しているんじゃないかな。リーダライタがカードを認識してコマンド・レスポンスをやり取りするのは同じです。

で、Nexus Sはこんな感じなので、日本のおサイフケータイの仕組みとは合いません。

接触ICカードの規格上、リーダライタはリーダライタ相手に通信しません(できません)。

NFCにはアクティブモードといって、両方が搬送波を出して通信するモードがあるのですが、現在日本で普及しているリーダライタ(改札機など)がそれに対応しているかどうか、リーダライタモードと共存できるのかなどについてまではよく知りません。詳しい人、教えてください。

ということで、GingerbreadでNFC対応だ!ってのは日本のおサイフケータイとどうやって共存していくのかが私にはまだよくわかりません。たぶん端末開発している人たちや、フェリカな人たちなんかがいろいろ頑張っているんじゃないかと思いますが。

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 さん、ありがとうございます!

ANDROID HACKS V.S. iOS SDK HACK

仕事柄、資料として技術書をたくさん買ってます。起業する前から技術書への投資は惜しまないようにしていたので、特に買う量が大きく増えたわけではありませんが。

その買った本の中には、オライリーから相次いで発売された AndroidiOSのHACKS本もあります。この二つは同じくHACKSという名前がついていますがかなり傾向の違う書籍となっているようです。

ANDROID HACKS

実はこの本、"HACKS"というタイトルの本なんですが、初心者もカバーする本となっています。なので、Javaはある程度わかるんだけど、Androidアプリケーションを作る勉強をしたいなって人はとりあえずこの本を買っておくというのも一つの手です。*1

この本、ページ数が550ページ超あるのですが、最初の184ページ目までは開発環境のインストールから初級アプリケーションの作成までのチュートリアルとなっています。

逆にP.439〜500はオープンソースで入手できるソースコードのビルド方法やカスタマイズ方法など、ごく普通のアプリ開発者には実はあまり必要じゃないかもしれないことが書かれています。*2

それ以外の部分は"HACKS"です。ただし、現状のAndroidの公式ドキュメントの説明が不十分でわかりにくい部分を補完する役割が大きな割合を占めているように感じます。しかし、それが今のAndroidに対する"HACKS"なのだろうと思います。

Androidは複数の機器メーカーが開発をしているため、すべての(互換性が保証された)端末で正しく動作するアプリケーションを作るためには、あまりマニアックなhackをやりすぎてしまうことは問題を引き起こすかもしれません。

しかし、将来的には次に紹介するiOS SDK HACKS並に濃い本も出てきてくれるんじゃないでしょうか。というか出てきて欲しいです。

iOS SDK HACKS

一方、最近出たこちらは初心者をばっさりと切り捨てたものとなっています。ページ数は180ページ程度とANDROID HACKSと比べると1/3程度となります。ページ数1/3以下で価格は半分以上というところに日本の書籍流通の不思議を感じてしまいます。同じ出版社さんなのに……。

すみません。ページ数が薄いからダメな本だと言っているつもりはありません。
SDKのインストールやツールの使い方などの初心者向けの内容をばっさりと切り捨てていることで、"HACKS"としての密度は濃いものとなっているのではないかと思います。

ほんの一例ですが、Objective-Cの動的な性質を利用し、Method Swizzlingでフレームワークのクラスライブラリに動的に機能を追加するようなテクニックについて説明されていたり、パフォーマンスチューニングにARMのSIMD命令を呼び出す方法を記載していたり……多少、やりすぎ感あふれてますね。私はこういうの大好きですが(笑)

すでに多数のアプリケーション開発入門書や良い本もiOS(iPhone)向けには出ているので、その隙間を狙うこの本のコンセプトは素晴らしいと思います。*3

書籍リンク

amazon:ANDROID HACKS
amazon:Google Androidプログラミング入門

amazon:iOS SDK HACKS

いずれも、今の開発に関しての周辺の状況を踏まえた上で作られた良書です。財布に余裕のある人はぜひとも買うことをオススメします。

*1:よりわかりやすい入門書としては、ASCIIさんから出ている「Google Android プログラミング入門」を勧めます。

*2:システムそのもののソースコードを読むとドキュメントでは足りない情報を入手できるので、先々必要になってきたりはするのですが。でも、それはAndroidでは一般の開発者向けのドキュメント類がまだ十分ではないということでもあります。

*3:ただし、そのまま使うとおそらく審査でリジェクトされるようなテクニックがいくつか見受けられるので、気をつけて使う必要がありそうです。

会社設立って必要?

ソフト作ってとりあえず食っていくだけなら要らないんじゃないですかね。

会社設立のメリットは、有限責任だとか節税ができるとか会社作りのハウツー本なんかにはいろいろ書いてますが、実際にはまだ設立したばかりで正直なところよくわかりません。

中小企業が借金するときに社長個人に連帯保証させることが多々あるようなので、株式会社にすると有限責任だからいいよ、ってのは(日本においては)ウソとは言わないまでも真実とも言えないんじゃないかと疑ってますが。

ただ、詳しいことは言えないのですが、個人ではやりにくい(出来ない)仕事ってのが存在するのは間違いありません。ここで言うのは個人のパワーでは出来ないという意味ではなくて、個人の「看板」では出来ないという意味です。

実際のところ、AndroidにしてもiPhoneにしても、大手ゲーム会社さんが販売しているような比較的規模の大きなソフトでもなければ、個人でも十分に開発していくことはできます。そういう世界をAppleGoogleが用意してくれました。今はとても個人の開発者にとってやりやすい時代です。

Android業界なんて特に、最近はいろんなアプリコンテストがあったり、キャリアさんも協力的だったり、個人でやっていくのにいい環境揃ってますよ。

なので、個人の看板でやっていけるお仕事だけやるんだったら、別に法人化って絶対に必要なものではないと思います。

ただ、優秀で学歴もあってある程度の年齢までのソフトウェア開発者さんは、Googleへの入社などにチャレンジした方がいいんじゃないかなと思いますよ。きっとやりがいも収入も素晴らしい世界があそこにはありますよ(笑)

会社作るのって大変?

ある意味大変なところはありますが、意外と簡単です。名前と事業内容さえ決めてしまえば、あとはインターネット上にたくさんある会社設立支援業務を行っているところに依頼すれば簡単に出来てしまいます。

私はこちらの「会社設立の申請書を無料作成!「会社設立サポーター」」にお願いをいたしました。先々の税務のことも考えると、単に設立だけをするところよりは会計士・税理士さんとしても相談できるところの方が良いとの判断です。

今の法律では0円資本金起業も可能ではあるのですが、やはり自分自身のお金を資本として入れることで覚悟というものが生まれること、ある程度の資本金が信用となることもあって、私はいくばくかの資本金を入れて設立をいたしました。

では、ある意味大変なのは何かというと、会社名とそれに対応するドメイン名を決めることです。これが本当に大変でした。

会社名の候補としていろいろ考えましたが、短い英単語を用いた会社名というのは世界中にたくさんあり、すでにドメインが取られているものが多々あります。その中にはとりあえず押さえられているだけで実体が存在しないようなものもあったりするのが非常に腹立たしかったですね。

検索しては衝突していることを確認し、また別の社名・ドメイン名を考え、検索し、を繰り返して、最終的には「ブライテクノ」という社名、brightechno.com というドメインを取得しました。

実はその過程でとりあえずで確保してあるドメインがいくつかあったりするので、私が逆に他の人のドメイン名検討を邪魔している可能性ありますね(笑)

さて、`他にもいろいろと書きたいことはあるけど、今日のところはここまで。つづきます。