├── .classpath ├── .gitignore ├── .project ├── AndroidManifest.xml ├── README.md ├── libs └── guavalib.jar ├── market ├── icon.png ├── icon_512.png ├── nfc-reader.apk ├── screenshot1.png └── screenshot2.png ├── project.properties ├── res ├── drawable-hdpi │ └── icon.png ├── drawable-ldpi │ └── icon.png ├── drawable-mdpi │ └── icon.png ├── layout │ ├── tag_divider.xml │ ├── tag_text.xml │ └── tag_viewer.xml ├── menu │ └── menu_main.xml ├── values-fr │ └── strings.xml └── values │ └── strings.xml └── src └── se └── anyro └── nfc_reader ├── NdefMessageParser.java ├── TagViewer.java └── record ├── ParsedNdefRecord.java ├── SmartPoster.java ├── TextRecord.java └── UriRecord.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | proguard.cfg 2 | default.properties 3 | bin/ 4 | gen/ 5 | *.DS_Store -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | nfc-reader 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 32 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Simple NFC reader for Android based on the sample code from the Android SDK. 2 | 3 | 4 | Get it on F-Droid 5 | 6 | Get it on Google Play 7 | 8 | If you have problem compiling the app make sure you have the /libs/guavalib.jar included in the build path. 9 | -------------------------------------------------------------------------------- /libs/guavalib.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadam/nfc-reader/716a0662c6fdf9e075422ee91f6f7f9c63341032/libs/guavalib.jar -------------------------------------------------------------------------------- /market/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadam/nfc-reader/716a0662c6fdf9e075422ee91f6f7f9c63341032/market/icon.png -------------------------------------------------------------------------------- /market/icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadam/nfc-reader/716a0662c6fdf9e075422ee91f6f7f9c63341032/market/icon_512.png -------------------------------------------------------------------------------- /market/nfc-reader.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadam/nfc-reader/716a0662c6fdf9e075422ee91f6f7f9c63341032/market/nfc-reader.apk -------------------------------------------------------------------------------- /market/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadam/nfc-reader/716a0662c6fdf9e075422ee91f6f7f9c63341032/market/screenshot1.png -------------------------------------------------------------------------------- /market/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadam/nfc-reader/716a0662c6fdf9e075422ee91f6f7f9c63341032/market/screenshot2.png -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-19 12 | -------------------------------------------------------------------------------- /res/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadam/nfc-reader/716a0662c6fdf9e075422ee91f6f7f9c63341032/res/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /res/drawable-ldpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadam/nfc-reader/716a0662c6fdf9e075422ee91f6f7f9c63341032/res/drawable-ldpi/icon.png -------------------------------------------------------------------------------- /res/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadam/nfc-reader/716a0662c6fdf9e075422ee91f6f7f9c63341032/res/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /res/layout/tag_divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | -------------------------------------------------------------------------------- /res/layout/tag_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | -------------------------------------------------------------------------------- /res/layout/tag_viewer.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 21 | 26 | 34 | 35 | -------------------------------------------------------------------------------- /res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | 12 | 15 | 18 | 21 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /res/values-fr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | Lecteur NFC 9 | C\'est une simple application pour lire divers tags (NDEF, RFID, FeliCa, ISO 14443, etc).\n\nVous l\'utilisez en tenant un tag ou une carte contre l\'arrière de votre appareil.\n\nVeuillez nous prévenir si vous voulez que nous changions comment les données sont affichées. Si vous avez un problème avec l\'application, envoyez nous un e-mail (adam@anyro.se) et nous vous dirons quand nous l\'aurons corrigé. Vous pouvez aussi étuider le code source et rapporter des problèmes sur https://github.com/nadam/nfc-reader.\n\nSi vous ne pouvez lire aucun tag, essayez d\'autres applications et dites nous si cette application est la seule à ne pas marcher. Merci ! 10 | Erreur 11 | Pas de NFC trouvé sur cet appareil 12 | Le NFC n\'est pas activé. Allez dans les paramètres "Sans fil et réseaux" pour l\'activer. 13 | Effacer tout 14 | Copier les IDs 15 | Hex 16 | Hex renversé 17 | Dec 18 | Dec renversé 19 | Rien de scanné pour le moment 20 | 21 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | NFC Reader 19 | This is a simple app to read various tags (NDEF, RFID, FeliCa, ISO 14443, etc).\n\nYou use it by holding a tag/card against the back of your device.\n\nPlease let me know if you want me to change how the data is displayed. Also send me an e-mail (adam@anyro.se) if you have any problems with the app and I\'ll let you know when I\'ve fixed it. You can also see the source code and report issues at https://github.com/nadam/nfc-reader.\n\nIf you can\'t read any tag, please try some of the other NFC apps on Google Play and let me know if this app is the only one not working. Thanks!\n\n/Adam 20 | Error 21 | No NFC found on this device 22 | NFC is not enabled. Please go to the wireless settings to enable it. 23 | Clear all 24 | Copy IDs 25 | Hex 26 | Reversed hex 27 | Dec 28 | Reversed dec 29 | Nothing scanned yet 30 | -------------------------------------------------------------------------------- /src/se/anyro/nfc_reader/NdefMessageParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package se.anyro.nfc_reader; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | import se.anyro.nfc_reader.record.ParsedNdefRecord; 22 | import se.anyro.nfc_reader.record.SmartPoster; 23 | import se.anyro.nfc_reader.record.TextRecord; 24 | import se.anyro.nfc_reader.record.UriRecord; 25 | import android.app.Activity; 26 | import android.nfc.NdefMessage; 27 | import android.nfc.NdefRecord; 28 | import android.view.LayoutInflater; 29 | import android.view.View; 30 | import android.view.ViewGroup; 31 | import android.widget.TextView; 32 | 33 | /** 34 | * Utility class for creating {@link ParsedNdefMessage}s. 35 | */ 36 | public class NdefMessageParser { 37 | 38 | // Utility class 39 | private NdefMessageParser() { 40 | 41 | } 42 | 43 | /** Parse an NdefMessage */ 44 | public static List parse(NdefMessage message) { 45 | return getRecords(message.getRecords()); 46 | } 47 | 48 | public static List getRecords(NdefRecord[] records) { 49 | List elements = new ArrayList(); 50 | for (final NdefRecord record : records) { 51 | if (UriRecord.isUri(record)) { 52 | elements.add(UriRecord.parse(record)); 53 | } else if (TextRecord.isText(record)) { 54 | elements.add(TextRecord.parse(record)); 55 | } else if (SmartPoster.isPoster(record)) { 56 | elements.add(SmartPoster.parse(record)); 57 | } else { 58 | elements.add(new ParsedNdefRecord() { 59 | @Override 60 | public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent, int offset) { 61 | TextView text = (TextView) inflater.inflate(R.layout.tag_text, parent, false); 62 | text.setText(new String(record.getPayload())); 63 | return text; 64 | } 65 | 66 | }); 67 | } 68 | } 69 | return elements; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/se/anyro/nfc_reader/TagViewer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * Copyright (C) 2011 Adam Nybäck 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package se.anyro.nfc_reader; 18 | 19 | import java.nio.charset.Charset; 20 | import java.text.DateFormat; 21 | import java.text.SimpleDateFormat; 22 | import java.util.ArrayList; 23 | import java.util.Date; 24 | import java.util.List; 25 | import java.util.Locale; 26 | 27 | import se.anyro.nfc_reader.record.ParsedNdefRecord; 28 | import android.app.Activity; 29 | import android.app.AlertDialog; 30 | import android.app.PendingIntent; 31 | import android.content.ClipData; 32 | import android.content.ClipboardManager; 33 | import android.content.DialogInterface; 34 | import android.content.Intent; 35 | import android.nfc.NdefMessage; 36 | import android.nfc.NdefRecord; 37 | import android.nfc.NfcAdapter; 38 | import android.nfc.Tag; 39 | import android.nfc.tech.MifareClassic; 40 | import android.nfc.tech.MifareUltralight; 41 | import android.nfc.tech.NfcA; 42 | import android.os.Bundle; 43 | import android.os.IBinder; 44 | import android.os.Parcel; 45 | import android.os.Parcelable; 46 | import android.provider.Settings; 47 | import android.view.LayoutInflater; 48 | import android.view.Menu; 49 | import android.view.MenuItem; 50 | import android.view.View; 51 | import android.widget.LinearLayout; 52 | import android.widget.TextView; 53 | import android.widget.Toast; 54 | 55 | /** 56 | * An {@link Activity} which handles a broadcast of a new tag that the device just discovered. 57 | */ 58 | public class TagViewer extends Activity { 59 | 60 | private static final DateFormat TIME_FORMAT = SimpleDateFormat.getDateTimeInstance(); 61 | private LinearLayout mTagContent; 62 | 63 | private NfcAdapter mAdapter; 64 | private PendingIntent mPendingIntent; 65 | private NdefMessage mNdefPushMessage; 66 | 67 | private AlertDialog mDialog; 68 | 69 | private List mTags = new ArrayList<>(); 70 | 71 | @Override 72 | protected void onCreate(Bundle savedInstanceState) { 73 | super.onCreate(savedInstanceState); 74 | setContentView(R.layout.tag_viewer); 75 | mTagContent = (LinearLayout) findViewById(R.id.list); 76 | resolveIntent(getIntent()); 77 | 78 | mDialog = new AlertDialog.Builder(this).setNeutralButton("Ok", null).create(); 79 | 80 | mAdapter = NfcAdapter.getDefaultAdapter(this); 81 | if (mAdapter == null) { 82 | showMessage(R.string.error, R.string.no_nfc); 83 | finish(); 84 | return; 85 | } 86 | 87 | mPendingIntent = PendingIntent.getActivity(this, 0, 88 | new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); 89 | mNdefPushMessage = new NdefMessage(new NdefRecord[] { newTextRecord( 90 | "Message from NFC Reader :-)", Locale.ENGLISH, true) }); 91 | } 92 | 93 | private void showMessage(int title, int message) { 94 | mDialog.setTitle(title); 95 | mDialog.setMessage(getText(message)); 96 | mDialog.show(); 97 | } 98 | 99 | private NdefRecord newTextRecord(String text, Locale locale, boolean encodeInUtf8) { 100 | byte[] langBytes = locale.getLanguage().getBytes(Charset.forName("US-ASCII")); 101 | 102 | Charset utfEncoding = encodeInUtf8 ? Charset.forName("UTF-8") : Charset.forName("UTF-16"); 103 | byte[] textBytes = text.getBytes(utfEncoding); 104 | 105 | int utfBit = encodeInUtf8 ? 0 : (1 << 7); 106 | char status = (char) (utfBit + langBytes.length); 107 | 108 | byte[] data = new byte[1 + langBytes.length + textBytes.length]; 109 | data[0] = (byte) status; 110 | System.arraycopy(langBytes, 0, data, 1, langBytes.length); 111 | System.arraycopy(textBytes, 0, data, 1 + langBytes.length, textBytes.length); 112 | 113 | return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], data); 114 | } 115 | 116 | @Override 117 | protected void onResume() { 118 | super.onResume(); 119 | if (mAdapter != null) { 120 | if (!mAdapter.isEnabled()) { 121 | showWirelessSettingsDialog(); 122 | } 123 | mAdapter.enableForegroundDispatch(this, mPendingIntent, null, null); 124 | mAdapter.enableForegroundNdefPush(this, mNdefPushMessage); 125 | } 126 | } 127 | 128 | @Override 129 | protected void onPause() { 130 | super.onPause(); 131 | if (mAdapter != null) { 132 | mAdapter.disableForegroundDispatch(this); 133 | mAdapter.disableForegroundNdefPush(this); 134 | } 135 | } 136 | 137 | private void showWirelessSettingsDialog() { 138 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 139 | builder.setMessage(R.string.nfc_disabled); 140 | builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 141 | public void onClick(DialogInterface dialogInterface, int i) { 142 | Intent intent = new Intent(Settings.ACTION_WIRELESS_SETTINGS); 143 | startActivity(intent); 144 | } 145 | }); 146 | builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { 147 | public void onClick(DialogInterface dialogInterface, int i) { 148 | finish(); 149 | } 150 | }); 151 | builder.create().show(); 152 | return; 153 | } 154 | 155 | private void resolveIntent(Intent intent) { 156 | String action = intent.getAction(); 157 | if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action) 158 | || NfcAdapter.ACTION_TECH_DISCOVERED.equals(action) 159 | || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) { 160 | Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); 161 | NdefMessage[] msgs; 162 | if (rawMsgs != null) { 163 | msgs = new NdefMessage[rawMsgs.length]; 164 | for (int i = 0; i < rawMsgs.length; i++) { 165 | msgs[i] = (NdefMessage) rawMsgs[i]; 166 | } 167 | } else { 168 | // Unknown tag type 169 | byte[] empty = new byte[0]; 170 | byte[] id = intent.getByteArrayExtra(NfcAdapter.EXTRA_ID); 171 | Tag tag = (Tag) intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); 172 | byte[] payload = dumpTagData(tag).getBytes(); 173 | NdefRecord record = new NdefRecord(NdefRecord.TNF_UNKNOWN, empty, id, payload); 174 | NdefMessage msg = new NdefMessage(new NdefRecord[] { record }); 175 | msgs = new NdefMessage[] { msg }; 176 | mTags.add(tag); 177 | } 178 | // Setup the views 179 | buildTagViews(msgs); 180 | } 181 | } 182 | 183 | private String dumpTagData(Tag tag) { 184 | StringBuilder sb = new StringBuilder(); 185 | byte[] id = tag.getId(); 186 | sb.append("ID (hex): ").append(toHex(id)).append('\n'); 187 | sb.append("ID (reversed hex): ").append(toReversedHex(id)).append('\n'); 188 | sb.append("ID (dec): ").append(toDec(id)).append('\n'); 189 | sb.append("ID (reversed dec): ").append(toReversedDec(id)).append('\n'); 190 | 191 | String prefix = "android.nfc.tech."; 192 | sb.append("Technologies: "); 193 | for (String tech : tag.getTechList()) { 194 | sb.append(tech.substring(prefix.length())); 195 | sb.append(", "); 196 | } 197 | sb.delete(sb.length() - 2, sb.length()); 198 | for (String tech : tag.getTechList()) { 199 | if (tech.equals(MifareClassic.class.getName())) { 200 | sb.append('\n'); 201 | String type = "Unknown"; 202 | try { 203 | MifareClassic mifareTag; 204 | try { 205 | mifareTag = MifareClassic.get(tag); 206 | } catch (Exception e) { 207 | // Fix for Sony Xperia Z3/Z5 phones 208 | tag = cleanupTag(tag); 209 | mifareTag = MifareClassic.get(tag); 210 | } 211 | switch (mifareTag.getType()) { 212 | case MifareClassic.TYPE_CLASSIC: 213 | type = "Classic"; 214 | break; 215 | case MifareClassic.TYPE_PLUS: 216 | type = "Plus"; 217 | break; 218 | case MifareClassic.TYPE_PRO: 219 | type = "Pro"; 220 | break; 221 | } 222 | sb.append("Mifare Classic type: "); 223 | sb.append(type); 224 | sb.append('\n'); 225 | 226 | sb.append("Mifare size: "); 227 | sb.append(mifareTag.getSize() + " bytes"); 228 | sb.append('\n'); 229 | 230 | sb.append("Mifare sectors: "); 231 | sb.append(mifareTag.getSectorCount()); 232 | sb.append('\n'); 233 | 234 | sb.append("Mifare blocks: "); 235 | sb.append(mifareTag.getBlockCount()); 236 | } catch (Exception e) { 237 | sb.append("Mifare classic error: " + e.getMessage()); 238 | } 239 | } 240 | 241 | if (tech.equals(MifareUltralight.class.getName())) { 242 | sb.append('\n'); 243 | MifareUltralight mifareUlTag = MifareUltralight.get(tag); 244 | String type = "Unknown"; 245 | switch (mifareUlTag.getType()) { 246 | case MifareUltralight.TYPE_ULTRALIGHT: 247 | type = "Ultralight"; 248 | break; 249 | case MifareUltralight.TYPE_ULTRALIGHT_C: 250 | type = "Ultralight C"; 251 | break; 252 | } 253 | sb.append("Mifare Ultralight type: "); 254 | sb.append(type); 255 | } 256 | } 257 | 258 | return sb.toString(); 259 | } 260 | 261 | private Tag cleanupTag(Tag oTag) { 262 | if (oTag == null) 263 | return null; 264 | 265 | String[] sTechList = oTag.getTechList(); 266 | 267 | Parcel oParcel = Parcel.obtain(); 268 | oTag.writeToParcel(oParcel, 0); 269 | oParcel.setDataPosition(0); 270 | 271 | int len = oParcel.readInt(); 272 | byte[] id = null; 273 | if (len >= 0) { 274 | id = new byte[len]; 275 | oParcel.readByteArray(id); 276 | } 277 | int[] oTechList = new int[oParcel.readInt()]; 278 | oParcel.readIntArray(oTechList); 279 | Bundle[] oTechExtras = oParcel.createTypedArray(Bundle.CREATOR); 280 | int serviceHandle = oParcel.readInt(); 281 | int isMock = oParcel.readInt(); 282 | IBinder tagService; 283 | if (isMock == 0) { 284 | tagService = oParcel.readStrongBinder(); 285 | } else { 286 | tagService = null; 287 | } 288 | oParcel.recycle(); 289 | 290 | int nfca_idx = -1; 291 | int mc_idx = -1; 292 | short oSak = 0; 293 | short nSak = 0; 294 | 295 | for (int idx = 0; idx < sTechList.length; idx++) { 296 | if (sTechList[idx].equals(NfcA.class.getName())) { 297 | if (nfca_idx == -1) { 298 | nfca_idx = idx; 299 | if (oTechExtras[idx] != null && oTechExtras[idx].containsKey("sak")) { 300 | oSak = oTechExtras[idx].getShort("sak"); 301 | nSak = oSak; 302 | } 303 | } else { 304 | if (oTechExtras[idx] != null && oTechExtras[idx].containsKey("sak")) { 305 | nSak = (short) (nSak | oTechExtras[idx].getShort("sak")); 306 | } 307 | } 308 | } else if (sTechList[idx].equals(MifareClassic.class.getName())) { 309 | mc_idx = idx; 310 | } 311 | } 312 | 313 | boolean modified = false; 314 | 315 | if (oSak != nSak) { 316 | oTechExtras[nfca_idx].putShort("sak", nSak); 317 | modified = true; 318 | } 319 | 320 | if (nfca_idx != -1 && mc_idx != -1 && oTechExtras[mc_idx] == null) { 321 | oTechExtras[mc_idx] = oTechExtras[nfca_idx]; 322 | modified = true; 323 | } 324 | 325 | if (!modified) { 326 | return oTag; 327 | } 328 | 329 | Parcel nParcel = Parcel.obtain(); 330 | nParcel.writeInt(id.length); 331 | nParcel.writeByteArray(id); 332 | nParcel.writeInt(oTechList.length); 333 | nParcel.writeIntArray(oTechList); 334 | nParcel.writeTypedArray(oTechExtras, 0); 335 | nParcel.writeInt(serviceHandle); 336 | nParcel.writeInt(isMock); 337 | if (isMock == 0) { 338 | nParcel.writeStrongBinder(tagService); 339 | } 340 | nParcel.setDataPosition(0); 341 | 342 | Tag nTag = Tag.CREATOR.createFromParcel(nParcel); 343 | 344 | nParcel.recycle(); 345 | 346 | return nTag; 347 | } 348 | 349 | private String toHex(byte[] bytes) { 350 | StringBuilder sb = new StringBuilder(); 351 | for (int i = bytes.length - 1; i >= 0; --i) { 352 | int b = bytes[i] & 0xff; 353 | if (b < 0x10) 354 | sb.append('0'); 355 | sb.append(Integer.toHexString(b)); 356 | if (i > 0) { 357 | sb.append(" "); 358 | } 359 | } 360 | return sb.toString(); 361 | } 362 | 363 | private String toReversedHex(byte[] bytes) { 364 | StringBuilder sb = new StringBuilder(); 365 | for (int i = 0; i < bytes.length; ++i) { 366 | if (i > 0) { 367 | sb.append(" "); 368 | } 369 | int b = bytes[i] & 0xff; 370 | if (b < 0x10) 371 | sb.append('0'); 372 | sb.append(Integer.toHexString(b)); 373 | } 374 | return sb.toString(); 375 | } 376 | 377 | private long toDec(byte[] bytes) { 378 | long result = 0; 379 | long factor = 1; 380 | for (int i = 0; i < bytes.length; ++i) { 381 | long value = bytes[i] & 0xffl; 382 | result += value * factor; 383 | factor *= 256l; 384 | } 385 | return result; 386 | } 387 | 388 | private long toReversedDec(byte[] bytes) { 389 | long result = 0; 390 | long factor = 1; 391 | for (int i = bytes.length - 1; i >= 0; --i) { 392 | long value = bytes[i] & 0xffl; 393 | result += value * factor; 394 | factor *= 256l; 395 | } 396 | return result; 397 | } 398 | 399 | void buildTagViews(NdefMessage[] msgs) { 400 | if (msgs == null || msgs.length == 0) { 401 | return; 402 | } 403 | LayoutInflater inflater = LayoutInflater.from(this); 404 | LinearLayout content = mTagContent; 405 | 406 | // Parse the first message in the list 407 | // Build views for all of the sub records 408 | Date now = new Date(); 409 | List records = NdefMessageParser.parse(msgs[0]); 410 | final int size = records.size(); 411 | for (int i = 0; i < size; i++) { 412 | TextView timeView = new TextView(this); 413 | timeView.setText(TIME_FORMAT.format(now)); 414 | content.addView(timeView, 0); 415 | ParsedNdefRecord record = records.get(i); 416 | content.addView(record.getView(this, inflater, content, i), 1 + i); 417 | content.addView(inflater.inflate(R.layout.tag_divider, content, false), 2 + i); 418 | } 419 | } 420 | 421 | @Override 422 | public boolean onCreateOptionsMenu(Menu menu) { 423 | getMenuInflater().inflate(R.menu.menu_main, menu); 424 | return true; 425 | } 426 | 427 | @Override 428 | public boolean onOptionsItemSelected(MenuItem item) { 429 | 430 | if (mTags.size() == 0) { 431 | Toast.makeText(this, R.string.nothing_scanned, Toast.LENGTH_LONG).show(); 432 | return true; 433 | } 434 | 435 | switch (item.getItemId()) { 436 | case R.id.menu_main_clear: 437 | clearTags(); 438 | return true; 439 | case R.id.menu_copy_hex: 440 | copyIds(getIdsHex()); 441 | return true; 442 | case R.id.menu_copy_reversed_hex: 443 | copyIds(getIdsReversedHex()); 444 | return true; 445 | case R.id.menu_copy_dec: 446 | copyIds(getIdsDec()); 447 | return true; 448 | case R.id.menu_copy_reversed_dec: 449 | copyIds(getIdsReversedDec()); 450 | return true; 451 | default: 452 | return super.onOptionsItemSelected(item); 453 | } 454 | } 455 | 456 | private void clearTags() { 457 | mTags.clear(); 458 | for (int i = mTagContent.getChildCount() -1; i >= 0 ; i--) { 459 | View view = mTagContent.getChildAt(i); 460 | if (view.getId() != R.id.tag_viewer_text) { 461 | mTagContent.removeViewAt(i); 462 | } 463 | } 464 | } 465 | 466 | private void copyIds(String text) { 467 | ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); 468 | ClipData clipData = ClipData.newPlainText("NFC IDs", text); 469 | clipboard.setPrimaryClip(clipData); 470 | Toast.makeText(this, mTags.size() + " IDs copied", Toast.LENGTH_SHORT).show(); 471 | } 472 | 473 | private String getIdsHex() { 474 | StringBuilder builder = new StringBuilder(); 475 | for (Tag tag : mTags) { 476 | builder.append(toHex(tag.getId())); 477 | builder.append('\n'); 478 | } 479 | builder.setLength(builder.length() - 1); // Remove last new line 480 | return builder.toString().replace(" ", ""); 481 | } 482 | 483 | private String getIdsReversedHex() { 484 | StringBuilder builder = new StringBuilder(); 485 | for (Tag tag : mTags) { 486 | builder.append(toReversedHex(tag.getId())); 487 | builder.append('\n'); 488 | } 489 | builder.setLength(builder.length() - 1); // Remove last new line 490 | return builder.toString().replace(" ", ""); 491 | } 492 | 493 | private String getIdsDec() { 494 | StringBuilder builder = new StringBuilder(); 495 | for (Tag tag : mTags) { 496 | builder.append(toDec(tag.getId())); 497 | builder.append('\n'); 498 | } 499 | builder.setLength(builder.length() - 1); // Remove last new line 500 | return builder.toString(); 501 | } 502 | 503 | private String getIdsReversedDec() { 504 | StringBuilder builder = new StringBuilder(); 505 | for (Tag tag : mTags) { 506 | builder.append(toReversedDec(tag.getId())); 507 | builder.append('\n'); 508 | } 509 | builder.setLength(builder.length() - 1); // Remove last new line 510 | return builder.toString(); 511 | } 512 | 513 | @Override 514 | public void onNewIntent(Intent intent) { 515 | setIntent(intent); 516 | resolveIntent(intent); 517 | } 518 | } -------------------------------------------------------------------------------- /src/se/anyro/nfc_reader/record/ParsedNdefRecord.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package se.anyro.nfc_reader.record; 18 | 19 | import android.app.Activity; 20 | import android.view.LayoutInflater; 21 | import android.view.View; 22 | import android.view.ViewGroup; 23 | 24 | 25 | public interface ParsedNdefRecord { 26 | 27 | /** 28 | * Returns a view to display this record. 29 | */ 30 | public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent, 31 | int offset); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/se/anyro/nfc_reader/record/SmartPoster.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package se.anyro.nfc_reader.record; 17 | 18 | import java.util.Arrays; 19 | import java.util.NoSuchElementException; 20 | 21 | import se.anyro.nfc_reader.NdefMessageParser; 22 | import se.anyro.nfc_reader.R; 23 | import android.app.Activity; 24 | import android.nfc.FormatException; 25 | import android.nfc.NdefMessage; 26 | import android.nfc.NdefRecord; 27 | import android.view.LayoutInflater; 28 | import android.view.View; 29 | import android.view.ViewGroup; 30 | import android.view.ViewGroup.LayoutParams; 31 | import android.widget.LinearLayout; 32 | 33 | import com.google.common.base.Charsets; 34 | import com.google.common.base.Preconditions; 35 | import com.google.common.collect.ImmutableMap; 36 | import com.google.common.collect.Iterables; 37 | 38 | /** 39 | * A representation of an NFC Forum "Smart Poster". 40 | */ 41 | public class SmartPoster implements ParsedNdefRecord { 42 | 43 | /** 44 | * NFC Forum Smart Poster Record Type Definition section 3.2.1. 45 | * 46 | * "The Title record for the service (there can be many of these in 47 | * different languages, but a language MUST NOT be repeated). This record is 48 | * optional." 49 | */ 50 | private final TextRecord mTitleRecord; 51 | 52 | /** 53 | * NFC Forum Smart Poster Record Type Definition section 3.2.1. 54 | * 55 | * "The URI record. This is the core of the Smart Poster, and all other 56 | * records are just metadata about this record. There MUST be one URI record 57 | * and there MUST NOT be more than one." 58 | */ 59 | private final UriRecord mUriRecord; 60 | 61 | /** 62 | * NFC Forum Smart Poster Record Type Definition section 3.2.1. 63 | * 64 | * "The Action record. This record describes how the service should be 65 | * treated. For example, the action may indicate that the device should save 66 | * the URI as a bookmark or open a browser. The Action record is optional. 67 | * If it does not exist, the device may decide what to do with the service. 68 | * If the action record exists, it should be treated as a strong suggestion; 69 | * the UI designer may ignore it, but doing so will induce a different user 70 | * experience from device to device." 71 | */ 72 | private final RecommendedAction mAction; 73 | 74 | /** 75 | * NFC Forum Smart Poster Record Type Definition section 3.2.1. 76 | * 77 | * "The Type record. If the URI references an external entity (e.g., via a 78 | * URL), the Type record may be used to declare the MIME type of the entity. 79 | * This can be used to tell the mobile device what kind of an object it can 80 | * expect before it opens the connection. The Type record is optional." 81 | */ 82 | private final String mType; 83 | 84 | private SmartPoster(UriRecord uri, TextRecord title, RecommendedAction action, String type) { 85 | mUriRecord = Preconditions.checkNotNull(uri); 86 | mTitleRecord = title; 87 | mAction = Preconditions.checkNotNull(action); 88 | mType = type; 89 | } 90 | 91 | public UriRecord getUriRecord() { 92 | return mUriRecord; 93 | } 94 | 95 | /** 96 | * Returns the title of the smart poster. This may be {@code null}. 97 | */ 98 | public TextRecord getTitle() { 99 | return mTitleRecord; 100 | } 101 | 102 | public static SmartPoster parse(NdefRecord record) { 103 | Preconditions.checkArgument(record.getTnf() == NdefRecord.TNF_WELL_KNOWN); 104 | Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_SMART_POSTER)); 105 | try { 106 | NdefMessage subRecords = new NdefMessage(record.getPayload()); 107 | return parse(subRecords.getRecords()); 108 | } catch (FormatException e) { 109 | throw new IllegalArgumentException(e); 110 | } 111 | } 112 | 113 | public static SmartPoster parse(NdefRecord[] recordsRaw) { 114 | try { 115 | Iterable records = NdefMessageParser.getRecords(recordsRaw); 116 | UriRecord uri = Iterables.getOnlyElement(Iterables.filter(records, UriRecord.class)); 117 | TextRecord title = getFirstIfExists(records, TextRecord.class); 118 | RecommendedAction action = parseRecommendedAction(recordsRaw); 119 | String type = parseType(recordsRaw); 120 | return new SmartPoster(uri, title, action, type); 121 | } catch (NoSuchElementException e) { 122 | throw new IllegalArgumentException(e); 123 | } 124 | } 125 | 126 | public static boolean isPoster(NdefRecord record) { 127 | try { 128 | parse(record); 129 | return true; 130 | } catch (IllegalArgumentException e) { 131 | return false; 132 | } 133 | } 134 | 135 | public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent, int offset) { 136 | if (mTitleRecord != null) { 137 | // Build a container to hold the title and the URI 138 | LinearLayout container = new LinearLayout(activity); 139 | container.setOrientation(LinearLayout.VERTICAL); 140 | container.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 141 | LayoutParams.WRAP_CONTENT)); 142 | container.addView(mTitleRecord.getView(activity, inflater, container, offset)); 143 | inflater.inflate(R.layout.tag_divider, container); 144 | container.addView(mUriRecord.getView(activity, inflater, container, offset)); 145 | return container; 146 | } else { 147 | // Just a URI, return a view for it directly 148 | return mUriRecord.getView(activity, inflater, parent, offset); 149 | } 150 | } 151 | 152 | /** 153 | * Returns the first element of {@code elements} which is an instance of 154 | * {@code type}, or {@code null} if no such element exists. 155 | */ 156 | private static T getFirstIfExists(Iterable elements, Class type) { 157 | Iterable filtered = Iterables.filter(elements, type); 158 | T instance = null; 159 | if (!Iterables.isEmpty(filtered)) { 160 | instance = Iterables.get(filtered, 0); 161 | } 162 | return instance; 163 | } 164 | 165 | private enum RecommendedAction { 166 | UNKNOWN((byte) -1), DO_ACTION((byte) 0), SAVE_FOR_LATER((byte) 1), OPEN_FOR_EDITING( 167 | (byte) 2); 168 | 169 | private static final ImmutableMap LOOKUP; 170 | static { 171 | ImmutableMap.Builder builder = ImmutableMap.builder(); 172 | for (RecommendedAction action : RecommendedAction.values()) { 173 | builder.put(action.getByte(), action); 174 | } 175 | LOOKUP = builder.build(); 176 | } 177 | 178 | private final byte mAction; 179 | 180 | private RecommendedAction(byte val) { 181 | this.mAction = val; 182 | } 183 | 184 | private byte getByte() { 185 | return mAction; 186 | } 187 | } 188 | 189 | private static NdefRecord getByType(byte[] type, NdefRecord[] records) { 190 | for (NdefRecord record : records) { 191 | if (Arrays.equals(type, record.getType())) { 192 | return record; 193 | } 194 | } 195 | return null; 196 | } 197 | 198 | private static final byte[] ACTION_RECORD_TYPE = new byte[] {'a', 'c', 't'}; 199 | 200 | private static RecommendedAction parseRecommendedAction(NdefRecord[] records) { 201 | NdefRecord record = getByType(ACTION_RECORD_TYPE, records); 202 | if (record == null) { 203 | return RecommendedAction.UNKNOWN; 204 | } 205 | byte action = record.getPayload()[0]; 206 | if (RecommendedAction.LOOKUP.containsKey(action)) { 207 | return RecommendedAction.LOOKUP.get(action); 208 | } 209 | return RecommendedAction.UNKNOWN; 210 | } 211 | 212 | private static final byte[] TYPE_TYPE = new byte[] {'t'}; 213 | 214 | private static String parseType(NdefRecord[] records) { 215 | NdefRecord type = getByType(TYPE_TYPE, records); 216 | if (type == null) { 217 | return null; 218 | } 219 | return new String(type.getPayload(), Charsets.UTF_8); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/se/anyro/nfc_reader/record/TextRecord.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | package se.anyro.nfc_reader.record; 17 | 18 | import java.io.UnsupportedEncodingException; 19 | import java.util.Arrays; 20 | 21 | import se.anyro.nfc_reader.R; 22 | import android.app.Activity; 23 | import android.nfc.NdefRecord; 24 | import android.view.LayoutInflater; 25 | import android.view.View; 26 | import android.view.ViewGroup; 27 | import android.widget.TextView; 28 | 29 | import com.google.common.base.Preconditions; 30 | 31 | /** 32 | * An NFC Text Record 33 | */ 34 | public class TextRecord implements ParsedNdefRecord { 35 | 36 | /** ISO/IANA language code */ 37 | private final String mLanguageCode; 38 | 39 | private final String mText; 40 | 41 | private TextRecord(String languageCode, String text) { 42 | mLanguageCode = Preconditions.checkNotNull(languageCode); 43 | mText = Preconditions.checkNotNull(text); 44 | } 45 | 46 | public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent, int offset) { 47 | TextView text = (TextView) inflater.inflate(R.layout.tag_text, parent, false); 48 | text.setText(mText); 49 | return text; 50 | } 51 | 52 | public String getText() { 53 | return mText; 54 | } 55 | 56 | /** 57 | * Returns the ISO/IANA language code associated with this text element. 58 | */ 59 | public String getLanguageCode() { 60 | return mLanguageCode; 61 | } 62 | 63 | // TODO: deal with text fields which span multiple NdefRecords 64 | public static TextRecord parse(NdefRecord record) { 65 | Preconditions.checkArgument(record.getTnf() == NdefRecord.TNF_WELL_KNOWN); 66 | Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_TEXT)); 67 | try { 68 | byte[] payload = record.getPayload(); 69 | /* 70 | * payload[0] contains the "Status Byte Encodings" field, per the 71 | * NFC Forum "Text Record Type Definition" section 3.2.1. 72 | * 73 | * bit7 is the Text Encoding Field. 74 | * 75 | * if (Bit_7 == 0): The text is encoded in UTF-8 if (Bit_7 == 1): 76 | * The text is encoded in UTF16 77 | * 78 | * Bit_6 is reserved for future use and must be set to zero. 79 | * 80 | * Bits 5 to 0 are the length of the IANA language code. 81 | */ 82 | String textEncoding = ((payload[0] & 0200) == 0) ? "UTF-8" : "UTF-16"; 83 | int languageCodeLength = payload[0] & 0077; 84 | String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII"); 85 | String text = 86 | new String(payload, languageCodeLength + 1, 87 | payload.length - languageCodeLength - 1, textEncoding); 88 | return new TextRecord(languageCode, text); 89 | } catch (UnsupportedEncodingException e) { 90 | // should never happen unless we get a malformed tag. 91 | throw new IllegalArgumentException(e); 92 | } 93 | } 94 | 95 | public static boolean isText(NdefRecord record) { 96 | try { 97 | parse(record); 98 | return true; 99 | } catch (IllegalArgumentException e) { 100 | return false; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/se/anyro/nfc_reader/record/UriRecord.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package se.anyro.nfc_reader.record; 17 | 18 | import java.nio.charset.Charset; 19 | import java.util.Arrays; 20 | 21 | import se.anyro.nfc_reader.R; 22 | import android.app.Activity; 23 | import android.net.Uri; 24 | import android.nfc.NdefRecord; 25 | import android.text.util.Linkify; 26 | import android.view.LayoutInflater; 27 | import android.view.View; 28 | import android.view.ViewGroup; 29 | import android.widget.TextView; 30 | 31 | import com.google.common.base.Preconditions; 32 | import com.google.common.collect.BiMap; 33 | import com.google.common.collect.ImmutableBiMap; 34 | import com.google.common.primitives.Bytes; 35 | 36 | /** 37 | * A parsed record containing a Uri. 38 | */ 39 | public class UriRecord implements ParsedNdefRecord { 40 | 41 | private static final String TAG = "UriRecord"; 42 | 43 | public static final String RECORD_TYPE = "UriRecord"; 44 | 45 | /** 46 | * NFC Forum "URI Record Type Definition" 47 | * 48 | * This is a mapping of "URI Identifier Codes" to URI string prefixes, 49 | * per section 3.2.2 of the NFC Forum URI Record Type Definition document. 50 | */ 51 | private static final BiMap URI_PREFIX_MAP = ImmutableBiMap.builder() 52 | .put((byte) 0x00, "") 53 | .put((byte) 0x01, "http://www.") 54 | .put((byte) 0x02, "https://www.") 55 | .put((byte) 0x03, "http://") 56 | .put((byte) 0x04, "https://") 57 | .put((byte) 0x05, "tel:") 58 | .put((byte) 0x06, "mailto:") 59 | .put((byte) 0x07, "ftp://anonymous:anonymous@") 60 | .put((byte) 0x08, "ftp://ftp.") 61 | .put((byte) 0x09, "ftps://") 62 | .put((byte) 0x0A, "sftp://") 63 | .put((byte) 0x0B, "smb://") 64 | .put((byte) 0x0C, "nfs://") 65 | .put((byte) 0x0D, "ftp://") 66 | .put((byte) 0x0E, "dav://") 67 | .put((byte) 0x0F, "news:") 68 | .put((byte) 0x10, "telnet://") 69 | .put((byte) 0x11, "imap:") 70 | .put((byte) 0x12, "rtsp://") 71 | .put((byte) 0x13, "urn:") 72 | .put((byte) 0x14, "pop:") 73 | .put((byte) 0x15, "sip:") 74 | .put((byte) 0x16, "sips:") 75 | .put((byte) 0x17, "tftp:") 76 | .put((byte) 0x18, "btspp://") 77 | .put((byte) 0x19, "btl2cap://") 78 | .put((byte) 0x1A, "btgoep://") 79 | .put((byte) 0x1B, "tcpobex://") 80 | .put((byte) 0x1C, "irdaobex://") 81 | .put((byte) 0x1D, "file://") 82 | .put((byte) 0x1E, "urn:epc:id:") 83 | .put((byte) 0x1F, "urn:epc:tag:") 84 | .put((byte) 0x20, "urn:epc:pat:") 85 | .put((byte) 0x21, "urn:epc:raw:") 86 | .put((byte) 0x22, "urn:epc:") 87 | .put((byte) 0x23, "urn:nfc:") 88 | .build(); 89 | 90 | private final Uri mUri; 91 | 92 | private UriRecord(Uri uri) { 93 | this.mUri = Preconditions.checkNotNull(uri); 94 | } 95 | 96 | public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent, int offset) { 97 | TextView text = (TextView) inflater.inflate(R.layout.tag_text, parent, false); 98 | text.setAutoLinkMask(Linkify.ALL); 99 | text.setText(mUri.toString()); 100 | return text; 101 | } 102 | 103 | public Uri getUri() { 104 | return mUri; 105 | } 106 | 107 | /** 108 | * Convert {@link android.nfc.NdefRecord} into a {@link android.net.Uri}. 109 | * This will handle both TNF_WELL_KNOWN / RTD_URI and TNF_ABSOLUTE_URI. 110 | * 111 | * @throws IllegalArgumentException if the NdefRecord is not a record 112 | * containing a URI. 113 | */ 114 | public static UriRecord parse(NdefRecord record) { 115 | short tnf = record.getTnf(); 116 | if (tnf == NdefRecord.TNF_WELL_KNOWN) { 117 | return parseWellKnown(record); 118 | } else if (tnf == NdefRecord.TNF_ABSOLUTE_URI) { 119 | return parseAbsolute(record); 120 | } 121 | throw new IllegalArgumentException("Unknown TNF " + tnf); 122 | } 123 | 124 | /** Parse and absolute URI record */ 125 | private static UriRecord parseAbsolute(NdefRecord record) { 126 | byte[] payload = record.getPayload(); 127 | Uri uri = Uri.parse(new String(payload, Charset.forName("UTF-8"))); 128 | return new UriRecord(uri); 129 | } 130 | 131 | /** Parse an well known URI record */ 132 | private static UriRecord parseWellKnown(NdefRecord record) { 133 | Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_URI)); 134 | byte[] payload = record.getPayload(); 135 | /* 136 | * payload[0] contains the URI Identifier Code, per the 137 | * NFC Forum "URI Record Type Definition" section 3.2.2. 138 | * 139 | * payload[1]...payload[payload.length - 1] contains the rest of 140 | * the URI. 141 | */ 142 | String prefix = URI_PREFIX_MAP.get(payload[0]); 143 | byte[] fullUri = 144 | Bytes.concat(prefix.getBytes(Charset.forName("UTF-8")), Arrays.copyOfRange(payload, 1, 145 | payload.length)); 146 | Uri uri = Uri.parse(new String(fullUri, Charset.forName("UTF-8"))); 147 | return new UriRecord(uri); 148 | } 149 | 150 | public static boolean isUri(NdefRecord record) { 151 | try { 152 | parse(record); 153 | return true; 154 | } catch (IllegalArgumentException e) { 155 | return false; 156 | } 157 | } 158 | 159 | private static final byte[] EMPTY = new byte[0]; 160 | } 161 | --------------------------------------------------------------------------------