409 lines
15 KiB
Java
409 lines
15 KiB
Java
/*
|
|
* Copyright (C) 2010 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package android.nfc.tech;
|
|
|
|
import android.nfc.ErrorCodes;
|
|
import android.nfc.FormatException;
|
|
import android.nfc.INfcTag;
|
|
import android.nfc.NdefMessage;
|
|
import android.nfc.Tag;
|
|
import android.nfc.TagLostException;
|
|
import android.os.Bundle;
|
|
import android.os.RemoteException;
|
|
import android.util.Log;
|
|
|
|
import java.io.IOException;
|
|
|
|
/**
|
|
* Provides access to NDEF content and operations on a {@link Tag}.
|
|
*
|
|
* <p>Acquire a {@link Ndef} object using {@link #get}.
|
|
*
|
|
* <p>NDEF is an NFC Forum data format. The data formats are implemented in
|
|
* {@link android.nfc.NdefMessage} and
|
|
* {@link android.nfc.NdefRecord}. This class provides methods to
|
|
* retrieve and modify the {@link android.nfc.NdefMessage}
|
|
* on a tag.
|
|
*
|
|
* <p>There are currently four NFC Forum standardized tag types that can be
|
|
* formatted to contain NDEF data.
|
|
* <ul>
|
|
* <li>NFC Forum Type 1 Tag ({@link #NFC_FORUM_TYPE_1}), such as the Innovision Topaz
|
|
* <li>NFC Forum Type 2 Tag ({@link #NFC_FORUM_TYPE_2}), such as the NXP MIFARE Ultralight
|
|
* <li>NFC Forum Type 3 Tag ({@link #NFC_FORUM_TYPE_3}), such as Sony Felica
|
|
* <li>NFC Forum Type 4 Tag ({@link #NFC_FORUM_TYPE_4}), such as NXP MIFARE Desfire
|
|
* </ul>
|
|
* It is mandatory for all Android devices with NFC to correctly enumerate
|
|
* {@link Ndef} on NFC Forum Tag Types 1-4, and implement all NDEF operations
|
|
* as defined in this class.
|
|
*
|
|
* <p>Some vendors have their own well defined specifications for storing NDEF data
|
|
* on tags that do not fall into the above categories. Android devices with NFC
|
|
* should enumerate and implement {@link Ndef} under these vendor specifications
|
|
* where possible, but it is not mandatory. {@link #getType} returns a String
|
|
* describing this specification, for example {@link #MIFARE_CLASSIC} is
|
|
* <code>com.nxp.ndef.mifareclassic</code>.
|
|
*
|
|
* <p>Android devices that support MIFARE Classic must also correctly
|
|
* implement {@link Ndef} on MIFARE Classic tags formatted to NDEF.
|
|
*
|
|
* <p>For guaranteed compatibility across all Android devices with NFC, it is
|
|
* recommended to use NFC Forum Types 1-4 in new deployments of NFC tags
|
|
* with NDEF payload. Vendor NDEF formats will not work on all Android devices.
|
|
*
|
|
* <p class="note"><strong>Note:</strong> Methods that perform I/O operations
|
|
* require the {@link android.Manifest.permission#NFC} permission.
|
|
*/
|
|
public final class Ndef extends BasicTagTechnology {
|
|
private static final String TAG = "NFC";
|
|
|
|
/** @hide */
|
|
public static final int NDEF_MODE_READ_ONLY = 1;
|
|
/** @hide */
|
|
public static final int NDEF_MODE_READ_WRITE = 2;
|
|
/** @hide */
|
|
public static final int NDEF_MODE_UNKNOWN = 3;
|
|
|
|
/** @hide */
|
|
public static final String EXTRA_NDEF_MSG = "ndefmsg";
|
|
|
|
/** @hide */
|
|
public static final String EXTRA_NDEF_MAXLENGTH = "ndefmaxlength";
|
|
|
|
/** @hide */
|
|
public static final String EXTRA_NDEF_CARDSTATE = "ndefcardstate";
|
|
|
|
/** @hide */
|
|
public static final String EXTRA_NDEF_TYPE = "ndeftype";
|
|
|
|
/** @hide */
|
|
public static final int TYPE_OTHER = -1;
|
|
/** @hide */
|
|
public static final int TYPE_1 = 1;
|
|
/** @hide */
|
|
public static final int TYPE_2 = 2;
|
|
/** @hide */
|
|
public static final int TYPE_3 = 3;
|
|
/** @hide */
|
|
public static final int TYPE_4 = 4;
|
|
/** @hide */
|
|
public static final int TYPE_MIFARE_CLASSIC = 101;
|
|
/** @hide */
|
|
public static final int TYPE_ICODE_SLI = 102;
|
|
|
|
/** @hide */
|
|
public static final String UNKNOWN = "android.ndef.unknown";
|
|
|
|
/** NFC Forum Tag Type 1 */
|
|
public static final String NFC_FORUM_TYPE_1 = "org.nfcforum.ndef.type1";
|
|
/** NFC Forum Tag Type 2 */
|
|
public static final String NFC_FORUM_TYPE_2 = "org.nfcforum.ndef.type2";
|
|
/** NFC Forum Tag Type 3 */
|
|
public static final String NFC_FORUM_TYPE_3 = "org.nfcforum.ndef.type3";
|
|
/** NFC Forum Tag Type 4 */
|
|
public static final String NFC_FORUM_TYPE_4 = "org.nfcforum.ndef.type4";
|
|
/** NDEF on MIFARE Classic */
|
|
public static final String MIFARE_CLASSIC = "com.nxp.ndef.mifareclassic";
|
|
/**
|
|
* NDEF on iCODE SLI
|
|
* @hide
|
|
*/
|
|
public static final String ICODE_SLI = "com.nxp.ndef.icodesli";
|
|
|
|
private final int mMaxNdefSize;
|
|
private final int mCardState;
|
|
private final NdefMessage mNdefMsg;
|
|
private final int mNdefType;
|
|
|
|
/**
|
|
* Get an instance of {@link Ndef} for the given tag.
|
|
*
|
|
* <p>Returns null if {@link Ndef} was not enumerated in {@link Tag#getTechList}.
|
|
* This indicates the tag is not NDEF formatted, or that this tag
|
|
* is NDEF formatted but under a vendor specification that this Android
|
|
* device does not implement.
|
|
*
|
|
* <p>Does not cause any RF activity and does not block.
|
|
*
|
|
* @param tag an NDEF compatible tag
|
|
* @return Ndef object
|
|
*/
|
|
public static Ndef get(Tag tag) {
|
|
if (!tag.hasTech(TagTechnology.NDEF)) return null;
|
|
try {
|
|
return new Ndef(tag);
|
|
} catch (RemoteException e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Internal constructor, to be used by NfcAdapter
|
|
* @hide
|
|
*/
|
|
public Ndef(Tag tag) throws RemoteException {
|
|
super(tag, TagTechnology.NDEF);
|
|
Bundle extras = tag.getTechExtras(TagTechnology.NDEF);
|
|
if (extras != null) {
|
|
mMaxNdefSize = extras.getInt(EXTRA_NDEF_MAXLENGTH);
|
|
mCardState = extras.getInt(EXTRA_NDEF_CARDSTATE);
|
|
mNdefMsg = extras.getParcelable(EXTRA_NDEF_MSG, android.nfc.NdefMessage.class);
|
|
mNdefType = extras.getInt(EXTRA_NDEF_TYPE);
|
|
} else {
|
|
throw new NullPointerException("NDEF tech extras are null.");
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Get the {@link NdefMessage} that was read from the tag at discovery time.
|
|
*
|
|
* <p>If the NDEF Message is modified by an I/O operation then it
|
|
* will not be updated here, this function only returns what was discovered
|
|
* when the tag entered the field.
|
|
* <p>Note that this method may return null if the tag was in the
|
|
* INITIALIZED state as defined by NFC Forum, as in this state the
|
|
* tag is formatted to support NDEF but does not contain a message yet.
|
|
* <p>Does not cause any RF activity and does not block.
|
|
* @return NDEF Message read from the tag at discovery time, can be null
|
|
*/
|
|
public NdefMessage getCachedNdefMessage() {
|
|
return mNdefMsg;
|
|
}
|
|
|
|
/**
|
|
* Get the NDEF tag type.
|
|
*
|
|
* <p>Returns one of {@link #NFC_FORUM_TYPE_1}, {@link #NFC_FORUM_TYPE_2},
|
|
* {@link #NFC_FORUM_TYPE_3}, {@link #NFC_FORUM_TYPE_4},
|
|
* {@link #MIFARE_CLASSIC} or another NDEF tag type that has not yet been
|
|
* formalized in this Android API.
|
|
*
|
|
* <p>Does not cause any RF activity and does not block.
|
|
*
|
|
* @return a string representing the NDEF tag type
|
|
*/
|
|
public String getType() {
|
|
switch (mNdefType) {
|
|
case TYPE_1:
|
|
return NFC_FORUM_TYPE_1;
|
|
case TYPE_2:
|
|
return NFC_FORUM_TYPE_2;
|
|
case TYPE_3:
|
|
return NFC_FORUM_TYPE_3;
|
|
case TYPE_4:
|
|
return NFC_FORUM_TYPE_4;
|
|
case TYPE_MIFARE_CLASSIC:
|
|
return MIFARE_CLASSIC;
|
|
case TYPE_ICODE_SLI:
|
|
return ICODE_SLI;
|
|
default:
|
|
return UNKNOWN;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the maximum NDEF message size in bytes.
|
|
*
|
|
* <p>Does not cause any RF activity and does not block.
|
|
*
|
|
* @return size in bytes
|
|
*/
|
|
public int getMaxSize() {
|
|
return mMaxNdefSize;
|
|
}
|
|
|
|
/**
|
|
* Determine if the tag is writable.
|
|
*
|
|
* <p>NFC Forum tags can be in read-only or read-write states.
|
|
*
|
|
* <p>Does not cause any RF activity and does not block.
|
|
*
|
|
* <p>Requires {@link android.Manifest.permission#NFC} permission.
|
|
*
|
|
* @return true if the tag is writable
|
|
*/
|
|
public boolean isWritable() {
|
|
return (mCardState == NDEF_MODE_READ_WRITE);
|
|
}
|
|
|
|
/**
|
|
* Read the current {@link android.nfc.NdefMessage} on this tag.
|
|
*
|
|
* <p>This always reads the current NDEF Message stored on the tag.
|
|
*
|
|
* <p>Note that this method may return null if the tag was in the
|
|
* INITIALIZED state as defined by NFC Forum, as in that state the
|
|
* tag is formatted to support NDEF but does not contain a message yet.
|
|
*
|
|
* <p>This is an I/O operation and will block until complete. It must
|
|
* not be called from the main application thread. A blocked call will be canceled with
|
|
* {@link IOException} if {@link #close} is called from another thread.
|
|
*
|
|
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
|
|
*
|
|
* @return the NDEF Message, can be null
|
|
* @throws TagLostException if the tag leaves the field
|
|
* @throws IOException if there is an I/O failure, or the operation is canceled
|
|
* @throws FormatException if the NDEF Message on the tag is malformed
|
|
* @throws SecurityException if the tag object is reused after the tag has left the field
|
|
*/
|
|
public NdefMessage getNdefMessage() throws IOException, FormatException {
|
|
checkConnected();
|
|
|
|
try {
|
|
INfcTag tagService = mTag.getTagService();
|
|
if (tagService == null) {
|
|
throw new IOException("Mock tags don't support this operation.");
|
|
}
|
|
int serviceHandle = mTag.getServiceHandle();
|
|
if (tagService.isNdef(serviceHandle)) {
|
|
NdefMessage msg = tagService.ndefRead(serviceHandle);
|
|
if (msg == null && !tagService.isPresent(serviceHandle)) {
|
|
throw new TagLostException();
|
|
}
|
|
return msg;
|
|
} else if (!tagService.isPresent(serviceHandle)) {
|
|
throw new TagLostException();
|
|
} else {
|
|
return null;
|
|
}
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "NFC service dead", e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Overwrite the {@link NdefMessage} on this tag.
|
|
*
|
|
* <p>This is an I/O operation and will block until complete. It must
|
|
* not be called from the main application thread. A blocked call will be canceled with
|
|
* {@link IOException} if {@link #close} is called from another thread.
|
|
*
|
|
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
|
|
*
|
|
* @param msg the NDEF Message to write, must not be null
|
|
* @throws TagLostException if the tag leaves the field
|
|
* @throws IOException if there is an I/O failure, or the operation is canceled
|
|
* @throws FormatException if the NDEF Message to write is malformed
|
|
* @throws SecurityException if the tag object is reused after the tag has left the field
|
|
*/
|
|
public void writeNdefMessage(NdefMessage msg) throws IOException, FormatException {
|
|
checkConnected();
|
|
|
|
try {
|
|
INfcTag tagService = mTag.getTagService();
|
|
if (tagService == null) {
|
|
throw new IOException("Mock tags don't support this operation.");
|
|
}
|
|
int serviceHandle = mTag.getServiceHandle();
|
|
if (tagService.isNdef(serviceHandle)) {
|
|
int errorCode = tagService.ndefWrite(serviceHandle, msg);
|
|
switch (errorCode) {
|
|
case ErrorCodes.SUCCESS:
|
|
break;
|
|
case ErrorCodes.ERROR_IO:
|
|
throw new IOException();
|
|
case ErrorCodes.ERROR_INVALID_PARAM:
|
|
throw new FormatException();
|
|
default:
|
|
// Should not happen
|
|
throw new IOException();
|
|
}
|
|
}
|
|
else {
|
|
throw new IOException("Tag is not ndef");
|
|
}
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "NFC service dead", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Indicates whether a tag can be made read-only with {@link #makeReadOnly()}.
|
|
*
|
|
* <p>Does not cause any RF activity and does not block.
|
|
*
|
|
* @return true if it is possible to make this tag read-only
|
|
* @throws SecurityException if the tag object is reused after the tag has left the field
|
|
*/
|
|
public boolean canMakeReadOnly() {
|
|
INfcTag tagService = mTag.getTagService();
|
|
if (tagService == null) {
|
|
return false;
|
|
}
|
|
try {
|
|
return tagService.canMakeReadOnly(mNdefType);
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "NFC service dead", e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Make a tag read-only.
|
|
*
|
|
* <p>This sets the CC field to indicate the tag is read-only,
|
|
* and where possible permanently sets the lock bits to prevent
|
|
* any further modification of the memory.
|
|
* <p>This is a one-way process and cannot be reverted!
|
|
*
|
|
* <p>This is an I/O operation and will block until complete. It must
|
|
* not be called from the main application thread. A blocked call will be canceled with
|
|
* {@link IOException} if {@link #close} is called from another thread.
|
|
*
|
|
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
|
|
*
|
|
* @return true on success, false if it is not possible to make this tag read-only
|
|
* @throws TagLostException if the tag leaves the field
|
|
* @throws IOException if there is an I/O failure, or the operation is canceled
|
|
* @throws SecurityException if the tag object is reused after the tag has left the field
|
|
*/
|
|
public boolean makeReadOnly() throws IOException {
|
|
checkConnected();
|
|
|
|
try {
|
|
INfcTag tagService = mTag.getTagService();
|
|
if (tagService == null) {
|
|
return false;
|
|
}
|
|
if (tagService.isNdef(mTag.getServiceHandle())) {
|
|
int errorCode = tagService.ndefMakeReadOnly(mTag.getServiceHandle());
|
|
switch (errorCode) {
|
|
case ErrorCodes.SUCCESS:
|
|
return true;
|
|
case ErrorCodes.ERROR_IO:
|
|
throw new IOException();
|
|
case ErrorCodes.ERROR_INVALID_PARAM:
|
|
return false;
|
|
default:
|
|
// Should not happen
|
|
throw new IOException();
|
|
}
|
|
}
|
|
else {
|
|
throw new IOException("Tag is not ndef");
|
|
}
|
|
} catch (RemoteException e) {
|
|
Log.e(TAG, "NFC service dead", e);
|
|
return false;
|
|
}
|
|
}
|
|
}
|