android21 NFCDemo 예제분석 2011.11.27 Café.naver.com/android21 김태성
NdefMessage 구조 NDEF란? NFC Data Exchange Format NFC 데이터통신프레임형식 NdefMessage 구조 - 단일 Record 구조 (Record 1 개 ) NdfRecord[0] - 다중 Record 구조 NdefRecord[0] NdefRecord[1] NdefRecord[2]... Record -내부 foramt에따라 Uri, Text, SmartPoster등규격에맞게여러가지정보포함가능 -위 3가지이외에도규격서를보면더다양한 format 존재 url 참고 - http://www.nfc-forum.org/specs/ forum org/specs/ 2
주요클래스 SmartPoster, TextRecord, UriRecord Class Record 에담긴정보의형식을나타내는클래스 소스코드에서주요한부분 parse 메소드 getview 메소드 3
AndroidManifest.xml 1/2 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.android.nfc"> com.example.android.nfc <!-- NFC 사용권한, 전화걸기권한허가 --> <uses-permission android:name="android.permission.nfc" /> <uses-permission android:name="android.permission.callp _ PHONE" /> <application android:icon="@drawable/icon" android:label="@string/app_name"> @ g <! 메인액티비티 FakeTagsActivity 등록--> <activity android:name=".simulator.faketagsactivity" android:theme="@android:style/theme.notitlebar"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" " d i t t t /> </intent-filter> </activity> it > 4
AndroidManifest.xml 2/2 <! 태그정보를표시할액티비티 TagViewer 등록--> <activity android:name="tagviewer" android:theme="@android:style/theme.notitlebar"> <! 태그를인식했을때표시되는액티비티 -> <intent-filter> <action android:name="android.nfc.action.tag_discovered" /> <category android:name="android.intent.category.default" /> </intent-filter> </activity> </application> <! 23 2.3 진저브레드이상에서 NFC 지원 --> <uses-sdk android:minsdkversion="9" /> <! NFC 디바이스사용등록--> <uses-feature android:name= "android.hardware.nfc" nfc" android:required= "true" /> </manifest> 5
res\layout\tag_divider.xml <?xml version="1.0" encoding="utf-8"?> <! 리스트항목에표시될레이아웃 -> <View xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?android:attr/listdivider" /> 6
res\layout\tag_text.xml <?xml version="1.0" encoding="utf-8"?> <! Text 태그를인식했을때표시할레이아웃-> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/text android:layout_width="match_parent" " h " android:minheight="?android:attr/listpreferreditemheight" android:layout_height="wrap_content" android:padding="4dip" ddi " android:textappearance="?android:attr/textappearancemedium" android:maxlines="5" android:ellipsize="end" end android:gravity="center_vertical" /> 7
res\layout\tag_viewer.xml <?xml version="1.0 0" encoding="utf utf-8 8"?> <! 태그뷰어액티비티의레이아웃--> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <! 태그타이틀한줄표시 --> <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:singleline="true" android:textappearance="?android:attr/textappearancemedium" android:textstyle="bold" android:shadowcolor="#bb000000" android:shadowradius="2.75" android:gravity="center_vertical" /> <! 스크롤가능하게내용표시 --> <ScrollView android:layout_width="match_parent" android:layout_height="0dip" android:layout_weight="1"> i <LinearLayout android:id="@+id/list" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" /> </ScrollView> </LinearLayout> 8
사운드리소스파일 태그인식사운드파일 태그를인식했을때플레이할사운드 res\raw\discovered_tag_notification.ogg 9
\res\values\strings.xml <?xml version="1.0" encoding="utf-8"?> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <! 앱의이름 --> <string name="app_name">nfcdemo</string> <! 태그가인식되었을때타이틀바에표시할액티비티의이름 --> <string name="title_scanned_tag">new tag collected</string> <! 텍스트형태그의제목 --> <string name="tag_text">text</string> </resources> 10
\src\com\example\android\nfc\ 소스파일및디렉토리 record : 태그데이터형식정보포함디렉토리 simulator : 메인액티비티포함 NdefMeeesgeParser : 인식된 Tag 의데이터분석클래스 TagViewer : 인식된태그표시액티비티 11
NFCDemo 소스코드분석 12
인식된태그정보표시액티비티 TAGVIEWER.JAVA 13
TagViewer.java 016: package com.example.android.nfc; 017: // 태그가인식되면정보를보여주는액티비티 018: import android.app.activity; 019: import android.content.intent; 020: import android.nfc.ndefmessage; 021: import android.nfc.ndefrecord; 022: import android.nfc.nfcadapter; 023: import android.os.bundle; 024: import android.os.parcelable; 025: import android.util.log; 026: import android.view.layoutinflater; 027: import android.view.windowmanager; 028: import android.widget.linearlayout; 029: import android.widget.textview; 030: 031: import com.example.android.nfc.record.parsedndefrecord; // 사용자구현 032: 033: import java.util.list; 034: 14
TagViewer.java 035: /** 036: * 스마트폰으로방금인식된새태그의브로드캐스트관리액티비티 038: */ 039: public class TagViewer extends Activity { 040: 041: static final String TAG = "ViewTag"; 042: 043: /** 044: * 이액티비티는사용자가아무것도하지않은경우자동종료 046: */ 047: static final int ACTIVITY_TIMEOUT_MS = 1 * 1000; // 1초대기 048: 049: TextView mtitle; // 태그의타이틀표시 050: 051: LinearLayout mtagcontent; // 태그의내용표시 052: 053: @Override 054: protected void oncreate(bundle savedinstancestate) { // 생성자 055: super.oncreate(savedinstancestate); 056: setcontentview(r.layout.tag_viewer); 057: mtagcontent t = (LinearLayout) L findviewbyid(r.id.list); id // 태그내용표시 058: mtitle = (TextView) findviewbyid(r.id.title); // 태그타이틀표시 059: resolveintent(getintent()); // 이액티비티가실행될때받았던인텐트분석후처리사용자메소드 060: } 061: 15
TagViewer.java 062: void resolveintent(intent intent) { 063: // 인텐트파싱 064: String action = intent.getaction(); 065: if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) _ ( { 066: // 태그가인식되면저장하기위해서비스를호출한다. 067: // PendingIntent를같이보내서다시내가불리게한다. 068: // 다시불릴때는 onnewintent() 호출되는데 069: // 그때 DB에서읽어서화면에표시한다. 070: Parcelable[] rawmsgs = intent.getparcelablearrayextra( NfcAdapter.EXTRA_NDEF_MESSAGES); NDEF 071: NdefMessage[] msgs; 072: if (rawmsgs!= null) { 073: msgs = new NdefMessage[rawMsgs.length]; 074: for (int i = 0; i < rawmsgs.length; i++) { // 모든태그메시지빼내기 075: msgs[i] = (NdefMessage) rawmsgs[i]; 076: } 16
TagViewer.java 077: } else { 078: // 알수없는태그형식일경우표시할메시지준비 079: byte[] empty = new byte[] {}; 080: NdefRecord record = new NdefRecord( NdefRecord.TNF_UNKNOWN, empty, empty, empty); 081: NdefMessage msg = new NdefMessage(new NdefRecord[] {record}); 082: msgs = new NdefMessage[] {msg}; 083: } 084: // 뷰설정하기 085: settitle(r.string.title_scanned_tag); string title scanned tag); // 인텐트에서빼낸정보화면표시 086: buildtagviews( msgs ); // 뷰에태그표시사용자메소드 087: } else { 088: Log.e(TAG, "Unknown intent " + intent); 089: finish(); 090: return; 091: } 092: } 093: 17
TagViewer.java 094: void buildtagviews(ndefmessage[] msgs) { // 태그정보화면표시 095: if (msgs == null msgs.length == 0) { 096: return; 097: } 098: LayoutInflater inflater = LayoutInflater.from(this); 099: LinearLayout content = mtagcontent; 100: // 연속으로두개태그를인식할수있기때문에 101: // 내용표시영역에이전내용을지운다. 102: content.removeallviews(); 103: // 리스트에첫번째메시지를 Parse 104: // 모든 sub 레코드의뷰들을생성 105: List<ParsedNdefRecord> records = NdefMessageParser.parse(msgs[0]); 106: final int size = records.size(); 107: for (int i = 0; i < size; i++) { // 모든나머지레코드에대해 108: ParsedNdefRecord record = records.get(i); 109: content.addview(record.getview(this, t d tvi (thi inflater, content, t i)); 110: inflater.inflate(r.layout.tag_divider, content, true); // 뷰생성 111: } 112: } 18
TagViewer.java 113: 114: @Override 115: public void onnewintent(intent intent) { // 새인텐츠가왔을때 116: setintent(intent); // 현재인텐츠로설정하고처리 117: resolveintent(intent); 118: } 119: 120: @Override 121: public void settitle(charsequence ence title) { // 타이틀설정 122: mtitle.settext(title); 123: } 124: } 19
태그메시지파서클래스 NDEF MESSAGE PARSER.JAVA 20
NdefMessageParser.java 016: package com.example.android.nfc; 017: 018: import android.nfc.ndefmessage; 019: import android.nfc.ndefrecord; 020: 021: import com.example.android.nfc.record.parsedndefrecord; 022: import com.example.android.nfc.record.smartposter; 023: import com.example.android.nfc.record.textrecord; android nfc record 024: import com.example.android.nfc.record.urirecord; 025: 026: import java.util.arraylist; 027: import java.util.list; 028: 029: /** 030: * ParsedNdefMessage들을만드는유틸리티클래스 031: */ 032: public class NdefMessageParser { 033: 034: // 생성자에서하는일은없음 035: private NdefMessageParser() { 036: 037: } 038: 21
NdefMessageParser.java 039: /** NdefMessage 파싱하기 */ 040: public static List<ParsedNdefRecord> parse(ndefmessage message) { 041: return getrecords(message.getrecords()); 042: } // NDEF 메시지에있는모든레코드를해체해서리스트로반환한다. 043: 044: public static List<ParsedNdefRecord> getrecords(ndefrecord[] records) { 045: List<ParsedNdefRecord> elements = new ArrayList<ParsedNdefRecord>(); 046: for (NdefRecord record : records) { // 모든레코드에대해 047: if (UriRecord.isUri(record)) { // URI 인경우 048: elementsadd(urirecord elements.add(urirecord.parse(record)); 049: } else if (TextRecord.isText(record)) { // 단순텍스트인경우 050: elements.add(textrecord.parse(record)); 051: } else if (SmartPoster.isPoster(record)) t P t ( { // 스마트포스터인경우 052: elements.add(smartposter.parse(record)); 053: } 054: } 055: return elements; 056: } 057: } 22
인터페이스 \RECORD\PARSEDNDEFRECORD.JAVA 23
\record\parsedndefrecord.java 017: package com.example.android.nfc.record; android nfc record; 018: 019: import android.app.activity; 020: import android.view.layoutinflater; 021: import android.view.view; 022: import android.view.viewgroup; 023: 024: 025: public interface ParsedNdefRecord { // URI든 Text든 Poster든공통구현 026: 027: /** 028: * 현재레코드를화면에표시하기위해반환 029: */ 030: public View getview(activity activity, LayoutInflater inflater, ViewGroup parent, 031: int offset); 032: 033: } 24
태그레코드형식 \RECORD\ SMART POSTER.JAVA 25
\record\smartposter.java 016: package com.example.android.nfc.record; 017: 018: import android.app.activity; 019: import android.nfc.formatexception; // NFC 패키지 020: import android.nfc.ndefmessage; f 021: import android.nfc.ndefrecord; 022: import android.view.layoutinflater; 023: import android.view.view; 024: import android.view.viewgroup; 025: import android.view.viewgroup.layoutparams; 026: import android.widget.linearlayout; 027: 028: import com.google.common.base.charsets; // 구아바패키지 029: import com.google.common.base.preconditions; 030: import com.google.common.collect.immutablemap; common collect 031: import com.google.common.collect.iterables; 032: 033: import com.example.android.nfc.ndefmessageparser; 034: import com.example.android.nfc.r; 035: 036: import java.util.arrays; 037: import java.util.nosuchelementexception; iln E i 038: 26
\record\smartposter.java 039: /** 040: * NFC 포럼의 "Smart Poster 에대한설명 041: */ 042: public class SmartPoster implements ParsedNdefRecord { 043: 044: /** 045: * NFC Forum의 Smart Poster 레코드형식은 section 3.2.1. 에정의 046: * 047: * 서비스에대한 Title record인데, 서비스는여러가지언어를사용할수있지만 048: * 언어필드는절대반복되어전송해서는안됨. 049: * 이 record 는옵션이므로사용하지않아도됨. 050: */ 051: private final TextRecord mtitlerecord; 052: 053: /** 054: * NFC Forum의 Smart Poster Record 형식은 section 3.2.1. 에정의 055: * 056: * URI record 는 Smart Poster 의핵심이다. 057: * 모든다른레코드들은이레코드에대한 meta( 보조 ) 데이터이다. 058: * 반드시하나의 URI 레코드만있어야하며하나를넘어도안된다. 059: */ 060: private final UriRecord murirecord; 27 061:
\record\smartposter.java 062: /** 063: * NFC Forum의 Smart Poster Record 형식 section 3.2.1. 에정의 064: * 065: * Action record는서비스가어떻게처리되어야하는지는나타낸다. 066: * 예를들면 action을디바이스가 URI의북마크를저장하거나웹브라우저를 067: * 열어야한다는것을나타낼수있다. Action record는옵션이다. 068: * 만약없을경우디바이스는서비스를통해어떻게처리할지결정한다. 069: * 만약액션레코드가있으면, 가장우선적으로처리한다. 070: * UI 디자이너가이를무시할수있지만, 071: * 그러면디바이스사이의사용자편의에차이가발생할수있다. 072: */ 073: private final RecommendedAction maction; 074: 075: /** 076: * NFC Forum의 Smart Poster Record 형식 section 3.2.1. 에정의 077: * 078: * Type record 변수인데, 만약 URI가 URL처럼외부에접근한다면 079: * Type record 는그것을가리키도록선언할수있다. 080: * 이것은디바이스에게연결을하기전에어떤종류의객체인지알려주는데사용할수있다. 081: * 이 Type record는옵션이다. 082: */ 083: private final String mtype; 28 084:
\record\smartposter.java 085: private SmartPoster( // 생성자 UriRecord uri, TextRecord title, RecommendedAction action, String type) { 086: murirecord = Preconditions.checkNotNull(uri); 087: mtitlerecord = title; 088: maction = Preconditions.checkNotNull(action); 089: mtype = type; 090: } 091: 092: public UriRecord geturirecord() { 093: return murirecord; 094: } 095: 096: /** 097: * smart poster의타이틀반환. null 이될수도있음. 옵션이니깐. 098: */ 099: public TextRecord gettitle() { 100: return mtitlerecord; 101: } 102: 29
\record\smartposter.java 103: public static SmartPoster parse(ndefrecord record) { // 파싱전에 TNF_WELL_KNOWN 이거나 RTD_SMART_POSTER인지체크 104: Preconditions.checkArgument( record.gettnf() == NdefRecord.TNF_WELL_KNOWN ); // checkargument() 메소드는필수조건검사후부적합 (false) 하면예외발생 105: Preconditions.checkArgument( Arrays.equals( record.gettype(), NdefRecord.RTD_SMART_POSTER ) ); 106: try { 107: NdefMessage subrecords = new NdefMessage(record.getPayload()); g ()); // 알맹이꺼내고 108: return parse(subrecords.getrecords()); // 메시지안에레코드들다시파싱 109: } catch (FormatException e) { 110: throw new IllegalArgumentException(e); // 형식미지원예외처리 111: } 112: } 113: 30
\record\smartposter.java // 원본레코드파싱 114: public static SmartPoster parse(ndefrecord[] recordsraw) { 115: try { // 순회를위해순회자얻기 116: Iterable<ParsedNdefRecord> records = NdefMessageParser.getRecords(recordsRaw); // URI 형식레코드만걸러서꺼내기 117: UriRecord uri = Iterables.getOnlyElement( Iterables.filter(records, UriRecord.class) ); // 옵션인텍스트레코드가있으면꺼내기 118: TextRecord title = getfirstifexists(records, TextRecord.class); // 액션꺼내기 119: RecommendedAction action = parserecommendedaction( recordsraw ); // 타입꺼내기 120: String type = parsetype(recordsraw); // 스마트포스터객체생성후반환 121: return new SmartPoster(uri, title, action, type); // 스마트포스터변환후반환 122: } catch (NoSuchElementException e) { 123: throw new IllegalArgumentException(e); // 예외처리 124: } 125: } 126: 31
\record\smartposter.java 127: public static boolean isposter(ndefrecord record) { // 스마트포스터인가? 128: try { 129: parse(record); // 파싱되면 130: return true; // 기다 131: } catch (IllegalArgumentException e) { 132: return false; 133: } 134: } 135: 32
\record\smartposter.java // 스마트포스터표시용레이아웃을만들어반환, 인테페이스필수구현메소드 136: public View getview(activity activity, LayoutInflater inflater, ViewGroup parent, int offset) { 137: if (mtitlerecord!= null) { // 타이틀이있으면 138: // title과 URI를가지는레이아웃만들기 139: LinearLayout container = new LinearLayout(activity); 140: container.setorientation(linearlayout.vertical); // 세로추가방향 141: container.setlayoutparams(new LayoutParams(LayoutParams.MATCH_PARENT, 142: LayoutParams.WRAP_CONTENT)); // 가로세로크기지정 143: container.addview(mtitlerecord.getview(activity, ( inflater, container, offset)); 144: inflater.inflate(r.layout.tag_divider, container); // XML로부터객체생성 145: container.addview(murirecord.getview(activity, inflater, container, offset)); 146: return container; 147: } else { 148: // URI의뷰만반환 149: return murirecord.getview(activity, inflater, parent, offset); 150: } 151: } 152: 33
\record\smartposter.java 153: /** 154: * 지정한 type 인스턴스들의첫번째것반환 155: * 없으면 null 반환 156: */ 157: private static <T> T getfirstifexists(iterable<?> elements, Class<T> type) { // 지정한형식의원소들만있는순회자생성 158: Iterable<T> filtered = Iterables.filter(elements, type); 159: T instance = null; // 반환할원소포인터생성 160: if (!Iterables.isEmpty(filtered) ) { // 객체들이있으면 161: instance = Iterables.get(filtered, 0); // 첫번째반환 162: } 163: return instance; 164: } 165: 34
\record\smartposter.java // Recommanded Action 설정값, 변수및메소드정의 166: private enum RecommendedAction { // enumeration 타입정의 167: UNKNOWN( (byte) -1 ), DO_ACTION( (byte) 0 ), 168: SAVE_FOR_LATER( (byte) 1 ), OPEN_FOR_EDITING( (byte) 2 ); 169: // 액션종류를맵에저장하기 170: private static final ImmutableMap<Byte, RecommendedAction> LOOKUP; 171: static { 172: ImmutableMap.Builder<Byte, RecommendedAction> builder = ImmutableMap.builder(); 173: for (RecommendedAction action : RecommendedAction.values()) { 174: builder.put(action.getbyte(), (), action); // 맵빌더에액션들저장 175: } 176: LOOKUP = builder.build(); // 맵에빌더로생성한액션들저장 177: } 178: 179: private final byte maction; // RecommendedAction 보관용 180: 181: private RecommendedAction(byte val) {// RecommendedAction 보관메소드 182: this.maction = val; 183: } 184: 35
\record\smartposter.java 185: private byte getbyte() { // Byte 타입의액션반환 186: return maction; 187: } 188: } 189: // 지정한타입의레코드검색메소드 190: private static NdefRecord getbytype(byte[] type, NdefRecord[] records) { // records 배열에서 type이같은레코드를꺼내반환 191: for ( NdefRecord record : records ) { 192: if ( Arrays.equals( type, record.gettype() gettype())){// ) ) 배열비교 193: return record; 194: } 195: } 196: return null; 197: } 198: 36
\record\smartposter.java 199: private static final byte[] ACTION_RECORD_TYPE TYPE = new byte[] {'a' {a, 'c', 't'}; t}; 200: // recommended 액션파싱 201: private static RecommendedAction parserecommendedaction( NdefRecord[] records) { // act 라는문자열의타입을가지는액션레코드가져오기 202: NdefRecord record = getbytype(action_record_type, records); 203: if (record == null) { 204: return RecommendedAction.UNKNOWN; // 알수없음 205: } 206: byte action = record.getpayload()[0]; // 알맹이꺼내기 207: if (RecommendedAction.LOOKUP.containsKey(action)) da t i K ( { 208: return RecommendedAction.LOOKUP.get(action); // 액션알려주기 209: } 210: return RecommendedAction.UNKNOWN; // 알수없음 211: } 212: 37
\record\smartposter.java 213: private static final byte[] TYPE_TYPE = new byte[] {'t'}; {t}; // t 는타입 214: // 타입꺼내기 215: private static String parsetype( NdefRecord[] records ) { // 타입레코드가져오기 216: NdefRecord type = getbytype( TYPE_TYPE, records); 217: if (type == null) { 218: return null; // 없음, 옵션이니까 219: } // 타입은문자열로표시가능 220: return new String(type.getPayload(), Charsets.UTF_8); 221: } 222: } 38
텍스트레코드클래스 \RECORD\ TEXT RECORD.JAVA 39
\record\textrecord.java 016: packagecom com.example.android.nfc.record; android nfc record; 017: 018: import android.app.activity; 019: import android.nfc.ndefrecord; 020: import android.view.layoutinflater; 021: import android.view.view; 022: import android.view.viewgroup; 023: import android.widget.textview; 024: 025: import com.google.common.base.preconditions; 026: 027: import com.example.android.nfc.r; 028: 029: import java.io.unsupportedencodingexception; 030: import java.util.arrays; 031: 40
\record\textrecord.java 032: /** 033: * NFC Text Record 034: */ 035: public class TextRecord implements ParsedNdefRecord { 036: 037: /** ISO/IANA 언어코드 */ 038: private final String mlanguagecode; 039: 040: private final String mtext; 041: // 생성자 042: private TextRecord(String languagecode, String text) { 043: mlanguagecode = Preconditions.checkNotNull(languageCode); 044: mtext = Preconditions.checkNotNull(text); 045: } 046: 41
\record\textrecord.java // 화면표시용뷰생성후반환 047: public View getview(activity activity, LayoutInflater inflater, ViewGroup parent, int offset) { 048: TextView text = (TextView) inflater.inflate(r.layout.tag_text, parent, false); 049: text.settext(mtext); 050: return text; 051: } 052: 053: public String gettext() { // 텍스트값반환 054: return mtext; 055: } 056: 057: /** 058: * 현재글자요소에관련된 ISO/IANA 언어코드반환 059: */ 060: public String getlanguagecode() { 061: return mlanguagecode; 062: } 063: 42
\record\textrecord.java 064: // TODO: deal with text fields which span multiple NdefRecords 065: public static TextRecord parse(ndefrecord record) { // 텍스트레코드변환 066: Preconditions.checkArgument(record.getTnf() == NdefRecord.TNF_WELL_KNOWN); 067: Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_TEXT)); 068: try { 069: byte[] payload = record.getpayload(); // 레코드알맹이정보빼내기 070: /* 071: * payload[0] 는 "Status Byte Encodings" 필드를포함한다. 072: * NFC Forum의 "Text Record Type Definition" section 3.2.1. 참조 073: * 074: * bit7 은 Text Encoding 필드 075: * 076: * if (Bit_7 == 0): 글자가 UTF-8 엔코딩 077: * if (Bit_7 == 1): 글자가 UTF16 엔코딩 078: * 079: * Bit_6 은예약비트이며, 0으로반드시초기화 080: * 081: * Bits 5 에서 0비트는 IANA 언어코드의길이 082: */ 43
\record\textrecord.java 083: String textencoding = ((payload[0] & 0200) == 0)? "UTF-8" : "UTF-16"; 084: int languagecodelength = payload[0] & 0077; 085: String languagecode = new String(payload, 1, languagecodelength, "US-ASCII"); // 알맹이정보를이용해텍스트만들기 086: String text = 087: new String(payload, languagecodelength + 1, 088: payload.length - languagecodelength - 1, textencoding); 089: return new TextRecord(languageCode, text); // 텍스트레코드생성후반환 090: } catch (UnsupportedEncodingException p e) ){ 091: // 손상된태그가아닌이상절대발생하지않음 092: throw new IllegalArgumentException(e); 093: } 094: } 44
\record\textrecord.java 095: 096: public static boolean istext(ndefrecord record) { // 텍스트형식인가? 097: try { 098: parse(record); // 파싱되면 099: return true; // 기다 100: } catch (IllegalArgumentException g e) ){ 101: return false; 102: } 103: } 104: } 45
URI 형식태그용레코드클래스 \RECORD\ URI RECORD.JAVA 46
\record\urirecord.java 016: packagecom.example.android.nfc.record; com.example.android.nfc.record; 017: // URI 형식의레코드타입처리클래스 018: import android.app.activity; 019: import android.net.uri; 020: import android.nfc.ndefrecord; 021: import android.view.layoutinflater; 022: import android.view.view; 023: import android.view.viewgroup; 024: import android.widget.textview; 025: 026: import com.google.common.base.preconditions; g 027: import com.google.common.collect.bimap; 028: import com.google.common.collect.immutablebimap; 029: import com.google.common.primitives.bytes; 030: 031: import com.example.android.nfc.r; 032: 033: import java.nio.charset.charset; 034: import java.util.arrays; 035: 47
\record\urirecord.java 036: /** 037: * Uri를포함하는파싱된레코드 038: */ 039: public class UriRecord implements ParsedNdefRecord { 040: 041: private static final String TAG = "UriRecord"; 042: 043: public static final String RECORD_TYPE = "UriRecord"; 044: 045: /** 046: * NFC Forum 의 "URI Record 형식정의" 047: * 048: * "URI Identifier Codes 를 URI 문자열접두어로매핑시킨것 049: * NFC Forum의 URI Record 형식정의문서 section 3.2.2 050: */ 051: private static final BiMap<Byte, String> URI_PREFIX_MAP = ImmutableBiMap.<Byte, String>builder() 052:.put((byte) 0x00, "") 053:.put((byte) 0x01, "http://www.") 054:.put((byte) 0x02, "https://www.") 48
\record\urirecord.java 055:.put((byte) 0x03, "http://") // 지원할여러가지 URI 레코드형식 056:.put((byte) 0x04, "https://") 057:.put((byte) 0x05, "tel:") 058:.put((byte) 0x06, "mailto:") 059:.put((byte) 0x07, "ftp://anonymous:anonymous@") 060:.put((byte) 0x08, "ftp://ftp.") 061:.put((byte) 0x09, "ftps://") 062:.put((byte) 0x0A, "sftp://") 063:.put((byte) 0x0B, "smb://") 064:.put((byte) 0x0C, "nfs://") 065:.put((byte) 0x0D, "ftp://") 066:.put((byte) 0x0E, "dav://") 067:.put((byte) t 0x0F, 0F "news:")") 068:.put((byte) 0x10, "telnet://") 069:.put((byte) 0x11, "imap:") 070:.put((byte) t 0x12, "rtsp://") 071:.put((byte) 0x13, "urn:") 49
\record\urirecord.java 072:.put((byte) 0x14, "pop:") // 여러가지 URI 레코드형식 073:.put((byte) 0x15, "sip:") 074:.put((byte) 0x16, "sips:") 075:.put((byte) 0x17, "tftp:") 076:.put((byte) 0x18, "btspp://") 077:.put((byte) 0x19, "btl2cap://") 078:.put((byte) 0x1A, "btgoep://") 079:.put((byte) 0x1B, "tcpobex://") 080:.put((byte) 0x1C, "irdaobex://") 081:.put((byte) 0x1D, "file://") 082:.put((byte) 0x1E, "urn:epc:id:") 083:.put((byte) 0x1F, "urn:epc:tag:") 084:.put((byte) t 0x20, "urn:epc:pat:") ") 085:.put((byte) 0x21, "urn:epc:raw:") 086:.put((byte) 0x22, "urn:epc:") 087:.put((byte) t 0x23, "urn:nfc:") ") 088:.build(); // 끝 089: 50
\record\urirecord.java 090: private final Uri muri; 091: 092: private UriRecord(Uri uri) { // 생성자 093: this.muri = Preconditions.checkNotNull(uri); 094: } 095: // 레코드정보표시를위한뷰생성, 인터페이스구현 096: public View getview(activity activity, LayoutInflater inflater, ViewGroup parent, int offset) { 097: TextView text = (TextView) inflater.inflate(r.layout.tag_text, inflate(rlayout tag text parent, false); 098: text.settext(muri.tostring()); 099: return text; 100: } 101: 102: public Uri geturi() { 103: return muri; 104: } 105: 51
\record\urirecord.java 106: /** 107: * android.nfc.ndefrecord를 android.net.uri로변환 108: * TNF_WELL_KNOWN / RTD_URI and TNF_ABSOLUTE_URI 모두처리 109: * 110: * @throws NdefRecord 가 URI를포함하지않으면 111: * IllegalArgumentException 예외발생 112: */ 113: public static UriRecord parse(ndefrecord record) { 114: short tnf = record.gettnf(); 115: if (tnf == NdefRecord.TNF_WELL_KNOWN) { // 잘알려진레코드형식 116: return parsewellknown(record); 117: } else if (tnf == NdefRecord.TNF_ABSOLUTE_URI) { // URI 절대주소형식 118: return parseabsolute(record); 119: } 120: throw new IllegalArgumentException("Unknown TNF " + tnf); // 알수없음 121: } 122: 52
\record\urirecord.java 123: /* 순수 URI record 파싱 */ 124: private static UriRecord parseabsolute(ndefrecord record) { 125: byte[] payload = record.getpayload(); // 레코드의알맹이꺼내기 126: Uri uri = Uri.parse(new String(payload, Charset.forName( forname("utf-8"))); 127: return new UriRecord(uri); 128: } 129: 53
\record\urirecord.java 130: /** 잘알려진 URI record 파싱 */ 131: private static UriRecord parsewellknown(ndefrecord record) { 132: Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_URI)); 133: byte[] payload = record.getpayload(); 134: /* 135: * payload[0] 는 URI Identifier Code를포함한다. 136: * NFC Forum의 "URI Record Type Definition" section 3.2.2. 참조 137: * 138: * payload[1]...payload[payload.length - 1] 는 URI의 139: * 나머지부분포함 140: */ 141: String prefix = URI_PREFIX_MAP.get(payload[0]); 142: byte[] fulluri = 143: Bytes.concat(prefix.getBytes(Charset.forName("UTF-8")), Arrays.copyOfRange(payload, 1, 144: payload.length)); 145: Uri uri = Uri.parse(new String(fullUri, Charset.forName("UTF-8"))); 146: return new UriRecord(uri); 147: } 54
\record\urirecord.java 148: 149: public static boolean isuri(ndefrecord record) { // URI 인가? 150: try { 151: parse(record); 152: return true; 153: } catch (IllegalArgumentException g e) { 154: return false; 155: } 156: } 157: 158: private static final byte[] EMPTY = new byte[0]; 159: } 55
메인액티비티 \SIMULATOR\ FAKE TAGS ACTIVITY.JAVA 56
\simulator\faketagsactivity.java 016: package com.example.android.nfc.simulator; 017: // 메인액티비티 018: import android.app.listactivity; 019: import android.content.intent; 020: import android.nfc.ndefmessage; 021: import android.nfc.ndefrecord; 022: import android.nfc.nfcadapter; 023: import android.os.bundle; 024: import android.view.view; 025: import android.widget.arrayadapter; 026: import android.widget.listview; 027: 028: import com.google.common.base.charsets; 029: import com.google.common.base.preconditions; 030: import com.google.common.primitives.bytes; 031: 032: import java.nio.charset.charset; 033: import java.util.locale; 034: 57
\simulator\faketagsactivity.java 035: /** 036: * 스캔된태그를실행하는메인액티비티 037: */ 038: public class FakeTagsActivity extends ListActivity { 039: 040: static final String TAG = "FakeTagsActivity"; // 디버깅용문자열 041: 042: static final byte[] UID = new byte[] {0x05, 0x00, 0x03, 0x08}; // 디바이스 UID 043: 044: ArrayAdapter<TagDescription> madapter; // 리스트뷰어댑터 045: 58
\simulator\faketagsactivity.java // 새태그만드는메소드 046: public static NdefRecord newtextrecord(string text, Locale locale, boolean encodeinutf8) { 047: Preconditions.checkNotNull(text); // 필수조건체크 048: Preconditions.checkNotNull(locale); // 레코드값준비 049: final byte[] langbytes = locale.getlanguage().getbytes(charsets.us_ascii); 050: final Charset utfencoding = encodeinutf8? Charsets.UTF_8 : Charset.forName( forname("utf-16 16"); 051: final byte[] textbytes = text.getbytes(utfencoding); 052: final int utfbit = encodeinutf8? 0 : (1 << 7); 053: final char status = (char) (utfbit + langbytes.length); g 054: final byte[] data = Bytes.concat(new byte[] {(byte) status}, langbytes, textbytes); 055: return new NdefRecord( NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], data); 056: } 057: 59
\simulator\faketagsactivity.java // 가짜 NdefRecord 만들어내기 058: public static NdefRecord newmimerecord(string type, byte[] data) { 059: Preconditions.checkNotNull(type); checknotnull(type); 060: Preconditions.checkNotNull(data); 061: final byte[] typebytes = type.getbytes(charsets.us_ascii); 062: return new NdefRecord(NdefRecord.TNF_MIME_MEDIA, typebytes, new byte[0], data); 063: } 064: 60
\simulator\faketagsactivity.java 065: static final class TagDescription { // 태그설명클래스 066: 067: public String title; // 제목 068: 069: public NdefMessage[] msgs; // NDEF 메시지 070: 071: public TagDescription(String title, byte[] bytes) {// 생성자 072: this.title = title; 073: try { 074: msgs = new NdefMessage[] {new NdefMessage(bytes)}; 075: } catch (final Exception e) { 076: throw new RuntimeException("Failed to create tag description", e); 077: } 078: } 079: 61
\simulator\faketagsactivity.java 080: @Override 081: public String tostring() { 082: return title; 083: } 084: } 085: // tostring 호출하면타이틀 ( 태그의이름 ) 돌려주기 62
\simulator\faketagsactivity.java 086: @Override 087: public void oncreate(bundle savedstate) { // 생성자 가짜 (Mock) 태그집어넣기 088: super.oncreate(savedstate); // 리스트뷰에표시할기본문자열들준비 089: final ArrayAdapter<TagDescription> adapter = new ArrayAdapter<TagDescription>( 090: this, android.r.layout.simple_list_item_1, android.r.id.text1); 091: adapter.add( 092: new TagDescription("Broadcast NFC Text Tag", MockNdefMessages.ENGLISH_PLAIN_TEXT)); 093: adapter.add(new TagDescription( 094: "Broadcast NFC SmartPoster URL & text", MockNdefMessages.SMART_POSTER_URL_AND_TEXT)); 095: adapter.add(new TagDescription( 096: "Broadcast NFC SmartPoster URL", MockNdefMessages.SMART_POSTER_URL_NO_TEXT)); 097: setlistadapter(adapter); 098: madapter = adapter; 099: } 63
\simulator\faketagsactivity.java 100: // 리스트뷰에서태그아이템선택했을때, TagViwer 액티비티로표시 101: @Override 102: public void onlistitemclick(listview l, View v, int position, long id) { // 태그설명가져오기 103: final TagDescription description = madapter.getitem(position); // 태그인식액션인텐트생성 104: final Intent intent = new Intent(NfcAdapter.ACTION_TAG_DISCOVERED); ACTION TAG // 태스설명을인텐트에집어넣기 105: intent.putextra(nfcadapter.extra_ndef_messages, description.msgs); // 인텐트처리액티비티실행 106: startactivity(intent); 107: } 108: } 64
가짜 NDEF 메시지클래스 \SIMULATOR\ MOCK NDEF MESSAGES.JAVA 65
\simulator\mockndefmessages.java 016: package com.example.android.nfc.simulator; 017: // 가짜 NDEF 포맷메시지 018: /** 019: * 이클래스는가짜 NFC Ndef 형식의태그목록을제공한다. 020: */ 021: public class MockNdefMessages { 022: 023: /** 024: * URL 을포함하고텍스트는포함하지않는 Smart Poster 025: */ 026: public static final byte[] SMART_POSTER_URL_NO_TEXT = 027: new byte[] {(byte) 0xd1, (byte) 0x02, (byte) 0x0f, (byte) 0x53, (byte) 0x70, (byte) 0xd1, 028: (byte) 0x01, (byte) 0x0b, (byte) 0x55, (byte) 0x01, (byte) 0x67, (byte) 0x6f, 029: (byte) 0x6f, (byte) 0x67, (byte) 0x6c, (byte) 0x65, (byte) 0x2e, (byte) 0x63, 030: (byte) 0x6f, (byte) 0x6d}; 031: 032: /** 033: * 영문으로된보통텍스트태그 034: */ 035: public static final byte[] ENGLISH_PLAIN_TEXT = 036: new byte[] {(byte) 0xd1, (byte) 0x01, 01 (byte) ) 0x1c, (byte) t ) 0x54, (byte) t ) 0x02, 02 (byte) t ) 0x65, 037: (byte) 0x6e, (byte) 0x53, (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x20, 038: (byte) 0x72, (byte) 0x61, (byte) 0x6e, (byte) 0x64, (byte) 0x6f, (byte) 0x6d, 039: (byte) 0x20, (byte) 0x65, (byte) 0x6e, (byte) 0x67, (byte) 0x6c, (byte) 0x69, 040: (byte) 0x73, (byte) 0x68, (byte) 0x20, (byte)) 0x74, (byte)) 0x65, (byte)) 0x78, 041: (byte) 0x74, (byte) 0x2e}; 66
\simulator\mockndefmessages.java 042: 043: /** 044: * URL과텍스트를포함하는스마트포스터 045: */ 046: public static final byte[] SMART_POSTER_URL_AND_TEXT = 047: new byte[] {(byte) 0xd1, (byte) 0x02, (byte) 0x1c, (byte) 0x53, (byte) 0x70, (byte) 0x91, 048: (byte) 0x01, (byte) 0x09, (byte) 0x54, (byte) 0x02, (byte) 0x65, (byte) 0x6e, 049: (byte) 0x47, (byte) 0x6f, (byte) 0x6f, (byte) 0x67, (byte) 0x6c, (byte) 0x65, 050: (byte) 0x51, (byte) 0x01, (byte) 0x0b, (byte) 0x55, (byte) 0x01, (byte) 0x67, 051: (byte) 0x6f, (byte) 0x6f, (byte) 0x67, (byte) 0x6c, (byte) 0x65, (byte) 0x2e, 052: (byte) 0x63, (byte) 0x6f, (byte) 0x6d}; 053: 054: /** 055: * 모든가짜 Ndef 태그들 056: */ 057: public static final byte[][] ALL_MOCK_MESSAGES = 058: new byte[][] {SMART_POSTER_URL_NO_TEXT, ENGLISH_PLAIN_TEXT, SMART_POSTER_URL_AND_TEXT}; AND TEXT}; 059: } 67