Bluetooth機器にタッチで接続出来るNFCタグを作る(その4)
前回紹介したBluetooth NFCタグライタで、いろいろなBluetooth機器用のNFC接続タグを作って試してみました。
結果、
スピーカー(Creative D100):OK
ヘッドセット(LBT-HS05):OK
キーボード(mac用):NG
MagicTrackpad(mac用):NG
iPhone:NG
:
どうやら、A2DPとHeadsetプロファイルにしか対応していなさそうです。
NFCタグにはUUID情報も書き込んでいるのに何故だ?と思い調べて見ました。
○NFCでの接続時のLogcatを取る。
関係ありそうな箇所を抜き出してみました。
D/NfcDispatcher(685): dispatch tag: TAG: Tech [android.nfc.tech.MifareUltralight, android.nfc.tech.NfcA, android.nfc.tech.Ndef] message: NdefMessage [NdefRecord tnf=2 type=6170706C69636174696F6E2F766E642E626C7565746F6F74682E65702E6F6F62 payload=22008A7E3A3C02000E0943726561746976652044313030040D04042405030B110E11] D/NfcHandover(685): tryHandover(): NdefMessage [NdefRecord tnf=2 type=6170706C69636174696F6E2F766E642E626C7565746F6F74682E65702E6F6F62 payload=22008A7E3A3C02000E0943726561746976652044313030040D04042405030B110E11] D/BluetoothAdapterProperties(1672): CONNECTION_STATE_CHANGE: 00:02:3C:3A:7E:8A: 0 -> 1 D/CachedBluetoothDevice(1599): onProfileStateChanged: profile HEADSET newProfileState 1 : D/CachedBluetoothDevice(1599): onProfileStateChanged: profile A2DP newProfileState 1 : D/CachedBluetoothDevice(1599): onProfileStateChanged: profile HEADSET newProfileState 0 : D/CachedBluetoothDevice(1599): onProfileStateChanged: profile A2DP newProfileState 2 : D/BluetoothAdapterProperties(1672): CONNECTION_STATE_CHANGE: 00:02:3C:3A:7E:8A: 1 -> 2 : D/CachedBluetoothDevice(1599): onProfileStateChanged: profile HEADSET newProfileState 1 : D/CachedBluetoothDevice(1599): onProfileStateChanged: profile HEADSET newProfileState 0
何故かA2DPとともにHEADSETプロファイルの接続シーケンスも走っているように見えます。
ソースコードを確認してみましょう。
その気になればソースコードを調べられるのがオープンソースの魅力です。
Androidソースコード検索サービスで、ログに出力されているキーワードを元に検索します。
ここでは、http://tools.oesf.biz/android-4.2.0_r1.0/を参照します。
結果、BluetoothとのNFCでの接続シーケンスは以下に実装されていました。
/src/com/android/nfc/handover/BluetoothHeadsetHandover.java
start()->nextStep()->nextStepConnect()と呼び出しながら接続シーケンスが実行され、
245 void nextStepConnect() { 246 switch (mState) { 260 : 261 case STATE_BONDING: 262 // Bluetooth Profile service will correctly serialize 263 // HFP then A2DP connect 264 mState = STATE_CONNECTING; 265 synchronized (mLock) { 266 if (mHeadset.getConnectionState(mDevice) != BluetoothProfile.STATE_CONNECTED) { 267 mHfpResult = RESULT_PENDING; 268 mHeadset.connect(mDevice); //★HeadSetの接続 269 } else { 270 mHfpResult = RESULT_CONNECTED; 271 } 272 if (mA2dp.getConnectionState(mDevice) != BluetoothProfile.STATE_CONNECTED) { 273 mA2dpResult = RESULT_PENDING; 274 mA2dp.connect(mDevice); // ★A2DPの接続 275 } else { 276 mA2dpResult = RESULT_CONNECTED; 277 } 278 if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) { 279 toast(mContext.getString(R.string.connecting_headset) + " " + mName + "..."); 280 break; 281 } 282 } 283 // fall-through
やはり、HEADSETとA2DPの接続しかしていない。それも、とにかく両方とも接続要求を出し、接続された方を利用するという実装の様です。
では、NFCに設定したUUIDは?NFCからのデータ読み込み部分を見てみましょう。
777 public boolean tryHandover(NdefMessage m) { 778 if (m == null) return false; 779 if (mBluetoothAdapter == null) return false; 780 781 if (DBG) Log.d(TAG, "tryHandover(): " + m.toString()); 782 783 BluetoothHandoverData handover = parse(m);//① 784 if (handover == null) return false; 785 if (!handover.valid) return true; 786 787 synchronized (HandoverManager.this) { 788 if (mBluetoothAdapter == null) { 789 if (DBG) Log.d(TAG, "BT handover, but BT not available"); 790 return true; 791 } 792 if (mBluetoothHeadsetHandover != null) { 793 if (DBG) Log.d(TAG, "BT handover already in progress, ignoring"); 794 return true; 795 } 796 mBluetoothHeadsetHandover = new BluetoothHeadsetHandover(mContext, handover.device, 797 handover.name, mHandoverPowerManager, this);//② 798 mBluetoothHeadsetHandover.start(); 799 } 800 return true; 801 }
①の箇所でNFCから取得したバイナリデータをパースし、BluetoothHandoverDataを生成しています。
BluetoothHeadsetHandoverが先ほどのBluetooth接続シーケンスを実装しているクラスです。
これに②で、①でのパース情報を渡しています。
では、①でのパース処理はどうなっているか。
/packages/apps/Nfc/src/com/android/nfc/handover/HandoverManager.java
916 BluetoothHandoverData parseBtOob(ByteBuffer payload) { 917 BluetoothHandoverData result = new BluetoothHandoverData(); 918 result.valid = false; 919 920 try { //①アドレス取得処理 921 payload.position(2); 922 byte[] address = new byte[6]; 923 payload.get(address); 924 // ByteBuffer.order(LITTLE_ENDIAN) doesn't work for 925 // ByteBuffer.get(byte[]), so manually swap order 926 for (int i = 0; i < 3; i++) { 927 byte temp = address[i]; 928 address[i] = address[5 - i]; 929 address[5 - i] = temp; 930 } 931 result.device = mBluetoothAdapter.getRemoteDevice(address); 932 result.valid = true; 933 //②デバイス名取得処理 934 while (payload.remaining() > 0) { 935 byte[] nameBytes; 936 int len = payload.get(); 937 int type = payload.get(); 938 switch (type) { 939 case 0x08: // short local name 940 nameBytes = new byte[len - 1]; 941 payload.get(nameBytes); 942 result.name = new String(nameBytes, Charset.forName("UTF-8")); 943 break; 944 case 0x09: // long local name 945 if (result.name != null) break; // prefer short name 946 nameBytes = new byte[len - 1]; 947 payload.get(nameBytes); 948 result.name = new String(nameBytes, Charset.forName("UTF-8")); 949 break; 950 default: 951 payload.position(payload.position() + len - 1); 952 break; 953 } 954 } 955 } catch (IllegalArgumentException e) { 956 Log.i(TAG, "BT OOB: invalid BT address"); 957 } catch (BufferUnderflowException e) { 958 Log.i(TAG, "BT OOB: payload shorter than expected"); 959 } 960 if (result.valid && result.name == null) result.name = ""; 961 return result; 962 }
①②のデバイスアドレス、デバイス名以外の情報は取得されていません。
折角NFCタグに書き込んだClass情報やUUID情報は無駄という訳です。
確かに、仕様書上はデバイスアドレス以外はオプション情報なのですが、設定されている場合は有効に利用してほしい物ですね。