Home | History | Annotate | Download | only in tech
      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 android.nfc.tech;
     18 
     19 import android.nfc.ErrorCodes;
     20 import android.nfc.FormatException;
     21 import android.nfc.INfcTag;
     22 import android.nfc.NdefMessage;
     23 import android.nfc.NfcAdapter;
     24 import android.nfc.Tag;
     25 import android.nfc.TagLostException;
     26 import android.os.Bundle;
     27 import android.os.RemoteException;
     28 import android.util.Log;
     29 
     30 import java.io.IOException;
     31 
     32 /**
     33  * Provides access to NDEF content and operations on a {@link Tag}.
     34  *
     35  * <p>Acquire a {@link Ndef} object using {@link #get}.
     36  *
     37  * <p>NDEF is an NFC Forum data format. The data formats are implemented in
     38  * {@link android.nfc.NdefMessage} and
     39  * {@link android.nfc.NdefRecord}. This class provides methods to
     40  * retrieve and modify the {@link android.nfc.NdefMessage}
     41  * on a tag.
     42  *
     43  * <p>There are currently four NFC Forum standardized tag types that can be
     44  * formatted to contain NDEF data.
     45  * <ul>
     46  * <li>NFC Forum Type 1 Tag ({@link #NFC_FORUM_TYPE_1}), such as the Innovision Topaz
     47  * <li>NFC Forum Type 2 Tag ({@link #NFC_FORUM_TYPE_2}), such as the NXP MIFARE Ultralight
     48  * <li>NFC Forum Type 3 Tag ({@link #NFC_FORUM_TYPE_3}), such as Sony Felica
     49  * <li>NFC Forum Type 4 Tag ({@link #NFC_FORUM_TYPE_4}), such as NXP MIFARE Desfire
     50  * </ul>
     51  * It is mandatory for all Android devices with NFC to correctly enumerate
     52  * {@link Ndef} on NFC Forum Tag Types 1-4, and implement all NDEF operations
     53  * as defined in this class.
     54  *
     55  * <p>Some vendors have there own well defined specifications for storing NDEF data
     56  * on tags that do not fall into the above categories. Android devices with NFC
     57  * should enumerate and implement {@link Ndef} under these vendor specifications
     58  * where possible, but it is not mandatory. {@link #getType} returns a String
     59  * describing this specification, for example {@link #MIFARE_CLASSIC} is
     60  * <code>com.nxp.ndef.mifareclassic</code>.
     61  *
     62  * <p>Android devices that support MIFARE Classic must also correctly
     63  * implement {@link Ndef} on MIFARE Classic tags formatted to NDEF.
     64  *
     65  * <p>For guaranteed compatibility across all Android devices with NFC, it is
     66  * recommended to use NFC Forum Types 1-4 in new deployments of NFC tags
     67  * with NDEF payload. Vendor NDEF formats will not work on all Android devices.
     68  *
     69  * <p class="note"><strong>Note:</strong> Methods that perform I/O operations
     70  * require the {@link android.Manifest.permission#NFC} permission.
     71  */
     72 public final class Ndef extends BasicTagTechnology {
     73     private static final String TAG = "NFC";
     74 
     75     /** @hide */
     76     public static final int NDEF_MODE_READ_ONLY = 1;
     77     /** @hide */
     78     public static final int NDEF_MODE_READ_WRITE = 2;
     79     /** @hide */
     80     public static final int NDEF_MODE_UNKNOWN = 3;
     81 
     82     /** @hide */
     83     public static final String EXTRA_NDEF_MSG = "ndefmsg";
     84 
     85     /** @hide */
     86     public static final String EXTRA_NDEF_MAXLENGTH = "ndefmaxlength";
     87 
     88     /** @hide */
     89     public static final String EXTRA_NDEF_CARDSTATE = "ndefcardstate";
     90 
     91     /** @hide */
     92     public static final String EXTRA_NDEF_TYPE = "ndeftype";
     93 
     94     /** @hide */
     95     public static final int TYPE_OTHER = -1;
     96     /** @hide */
     97     public static final int TYPE_1 = 1;
     98     /** @hide */
     99     public static final int TYPE_2 = 2;
    100     /** @hide */
    101     public static final int TYPE_3 = 3;
    102     /** @hide */
    103     public static final int TYPE_4 = 4;
    104     /** @hide */
    105     public static final int TYPE_MIFARE_CLASSIC = 101;
    106     /** @hide */
    107     public static final int TYPE_ICODE_SLI = 102;
    108 
    109     /** @hide */
    110     public static final String UNKNOWN = "android.ndef.unknown";
    111 
    112     /** NFC Forum Tag Type 1 */
    113     public static final String NFC_FORUM_TYPE_1 = "org.nfcforum.ndef.type1";
    114     /** NFC Forum Tag Type 2 */
    115     public static final String NFC_FORUM_TYPE_2 = "org.nfcforum.ndef.type2";
    116     /** NFC Forum Tag Type 4 */
    117     public static final String NFC_FORUM_TYPE_3 = "org.nfcforum.ndef.type3";
    118     /** NFC Forum Tag Type 4 */
    119     public static final String NFC_FORUM_TYPE_4 = "org.nfcforum.ndef.type4";
    120     /** NDEF on MIFARE Classic */
    121     public static final String MIFARE_CLASSIC = "com.nxp.ndef.mifareclassic";
    122     /**
    123      * NDEF on iCODE SLI
    124      * @hide
    125      */
    126     public static final String ICODE_SLI = "com.nxp.ndef.icodesli";
    127 
    128     private final int mMaxNdefSize;
    129     private final int mCardState;
    130     private final NdefMessage mNdefMsg;
    131     private final int mNdefType;
    132 
    133     /**
    134      * Get an instance of {@link Ndef} for the given tag.
    135      *
    136      * <p>Returns null if {@link Ndef} was not enumerated in {@link Tag#getTechList}.
    137      * This indicates the tag is not NDEF formatted, or that this tag
    138      * is NDEF formatted but under a vendor specification that this Android
    139      * device does not implement.
    140      *
    141      * <p>Does not cause any RF activity and does not block.
    142      *
    143      * @param tag an NDEF compatible tag
    144      * @return Ndef object
    145      */
    146     public static Ndef get(Tag tag) {
    147         if (!tag.hasTech(TagTechnology.NDEF)) return null;
    148         try {
    149             return new Ndef(tag);
    150         } catch (RemoteException e) {
    151             return null;
    152         }
    153     }
    154 
    155     /**
    156      * Internal constructor, to be used by NfcAdapter
    157      * @hide
    158      */
    159     public Ndef(Tag tag) throws RemoteException {
    160         super(tag, TagTechnology.NDEF);
    161         Bundle extras = tag.getTechExtras(TagTechnology.NDEF);
    162         if (extras != null) {
    163             mMaxNdefSize = extras.getInt(EXTRA_NDEF_MAXLENGTH);
    164             mCardState = extras.getInt(EXTRA_NDEF_CARDSTATE);
    165             mNdefMsg = extras.getParcelable(EXTRA_NDEF_MSG);
    166             mNdefType = extras.getInt(EXTRA_NDEF_TYPE);
    167         } else {
    168             throw new NullPointerException("NDEF tech extras are null.");
    169         }
    170 
    171     }
    172 
    173     /**
    174      * Get the {@link NdefMessage} that was read from the tag at discovery time.
    175      *
    176      * <p>If the NDEF Message is modified by an I/O operation then it
    177      * will not be updated here, this function only returns what was discovered
    178      * when the tag entered the field.
    179      * <p>Note that this method may return null if the tag was in the
    180      * INITIALIZED state as defined by NFC Forum, as in this state the
    181      * tag is formatted to support NDEF but does not contain a message yet.
    182      * <p>Does not cause any RF activity and does not block.
    183      * @return NDEF Message read from the tag at discovery time, can be null
    184      */
    185     public NdefMessage getCachedNdefMessage() {
    186         return mNdefMsg;
    187     }
    188 
    189     /**
    190      * Get the NDEF tag type.
    191      *
    192      * <p>Returns one of {@link #NFC_FORUM_TYPE_1}, {@link #NFC_FORUM_TYPE_2},
    193      * {@link #NFC_FORUM_TYPE_3}, {@link #NFC_FORUM_TYPE_4},
    194      * {@link #MIFARE_CLASSIC} or another NDEF tag type that has not yet been
    195      * formalized in this Android API.
    196      *
    197      * <p>Does not cause any RF activity and does not block.
    198      *
    199      * @return a string representing the NDEF tag type
    200      */
    201     public String getType() {
    202         switch (mNdefType) {
    203             case TYPE_1:
    204                 return NFC_FORUM_TYPE_1;
    205             case TYPE_2:
    206                 return NFC_FORUM_TYPE_2;
    207             case TYPE_3:
    208                 return NFC_FORUM_TYPE_3;
    209             case TYPE_4:
    210                 return NFC_FORUM_TYPE_4;
    211             case TYPE_MIFARE_CLASSIC:
    212                 return MIFARE_CLASSIC;
    213             case TYPE_ICODE_SLI:
    214                 return ICODE_SLI;
    215             default:
    216                 return UNKNOWN;
    217         }
    218     }
    219 
    220     /**
    221      * Get the maximum NDEF message size in bytes.
    222      *
    223      * <p>Does not cause any RF activity and does not block.
    224      *
    225      * @return size in bytes
    226      */
    227     public int getMaxSize() {
    228         return mMaxNdefSize;
    229     }
    230 
    231     /**
    232      * Determine if the tag is writable.
    233      *
    234      * <p>NFC Forum tags can be in read-only or read-write states.
    235      *
    236      * <p>Does not cause any RF activity and does not block.
    237      *
    238      * <p>Requires {@link android.Manifest.permission#NFC} permission.
    239      *
    240      * @return true if the tag is writable
    241      */
    242     public boolean isWritable() {
    243         return (mCardState == NDEF_MODE_READ_WRITE);
    244     }
    245 
    246     /**
    247      * Read the current {@link android.nfc.NdefMessage} on this tag.
    248      *
    249      * <p>This always reads the current NDEF Message stored on the tag.
    250      *
    251      * <p>Note that this method may return null if the tag was in the
    252      * INITIALIZED state as defined by NFC Forum, as in that state the
    253      * tag is formatted to support NDEF but does not contain a message yet.
    254      *
    255      * <p>This is an I/O operation and will block until complete. It must
    256      * not be called from the main application thread. A blocked call will be canceled with
    257      * {@link IOException} if {@link #close} is called from another thread.
    258      *
    259      * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
    260      *
    261      * @return the NDEF Message, can be null
    262      * @throws TagLostException if the tag leaves the field
    263      * @throws IOException if there is an I/O failure, or the operation is canceled
    264      * @throws FormatException if the NDEF Message on the tag is malformed
    265      */
    266     public NdefMessage getNdefMessage() throws IOException, FormatException {
    267         checkConnected();
    268 
    269         try {
    270             INfcTag tagService = mTag.getTagService();
    271             if (tagService == null) {
    272                 throw new IOException("Mock tags don't support this operation.");
    273             }
    274             int serviceHandle = mTag.getServiceHandle();
    275             if (tagService.isNdef(serviceHandle)) {
    276                 NdefMessage msg = tagService.ndefRead(serviceHandle);
    277                 if (msg == null && !tagService.isPresent(serviceHandle)) {
    278                     throw new TagLostException();
    279                 }
    280                 return msg;
    281             } else {
    282                 return null;
    283             }
    284         } catch (RemoteException e) {
    285             Log.e(TAG, "NFC service dead", e);
    286             return null;
    287         }
    288     }
    289 
    290     /**
    291      * Overwrite the {@link NdefMessage} on this tag.
    292      *
    293      * <p>This is an I/O operation and will block until complete. It must
    294      * not be called from the main application thread. A blocked call will be canceled with
    295      * {@link IOException} if {@link #close} is called from another thread.
    296      *
    297      * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
    298      *
    299      * @param msg the NDEF Message to write, must not be null
    300      * @throws TagLostException if the tag leaves the field
    301      * @throws IOException if there is an I/O failure, or the operation is canceled
    302      * @throws FormatException if the NDEF Message to write is malformed
    303      */
    304     public void writeNdefMessage(NdefMessage msg) throws IOException, FormatException {
    305         checkConnected();
    306 
    307         try {
    308             INfcTag tagService = mTag.getTagService();
    309             if (tagService == null) {
    310                 throw new IOException("Mock tags don't support this operation.");
    311             }
    312             int serviceHandle = mTag.getServiceHandle();
    313             if (tagService.isNdef(serviceHandle)) {
    314                 int errorCode = tagService.ndefWrite(serviceHandle, msg);
    315                 switch (errorCode) {
    316                     case ErrorCodes.SUCCESS:
    317                         break;
    318                     case ErrorCodes.ERROR_IO:
    319                         throw new IOException();
    320                     case ErrorCodes.ERROR_INVALID_PARAM:
    321                         throw new FormatException();
    322                     default:
    323                         // Should not happen
    324                         throw new IOException();
    325                 }
    326             }
    327             else {
    328                 throw new IOException("Tag is not ndef");
    329             }
    330         } catch (RemoteException e) {
    331             Log.e(TAG, "NFC service dead", e);
    332         }
    333     }
    334 
    335     /**
    336      * Indicates whether a tag can be made read-only with {@link #makeReadOnly()}.
    337      *
    338      * <p>Does not cause any RF activity and does not block.
    339      *
    340      * @return true if it is possible to make this tag read-only
    341      */
    342     public boolean canMakeReadOnly() {
    343         INfcTag tagService = mTag.getTagService();
    344         if (tagService == null) {
    345             return false;
    346         }
    347         try {
    348             return tagService.canMakeReadOnly(mNdefType);
    349         } catch (RemoteException e) {
    350             Log.e(TAG, "NFC service dead", e);
    351             return false;
    352         }
    353     }
    354 
    355     /**
    356      * Make a tag read-only.
    357      *
    358      * <p>This sets the CC field to indicate the tag is read-only,
    359      * and where possible permanently sets the lock bits to prevent
    360      * any further modification of the memory.
    361      * <p>This is a one-way process and cannot be reverted!
    362      *
    363      * <p>This is an I/O operation and will block until complete. It must
    364      * not be called from the main application thread. A blocked call will be canceled with
    365      * {@link IOException} if {@link #close} is called from another thread.
    366      *
    367      * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
    368      *
    369      * @return true on success, false if it is not possible to make this tag read-only
    370      * @throws TagLostException if the tag leaves the field
    371      * @throws IOException if there is an I/O failure, or the operation is canceled
    372      */
    373     public boolean makeReadOnly() throws IOException {
    374         checkConnected();
    375 
    376         try {
    377             INfcTag tagService = mTag.getTagService();
    378             if (tagService == null) {
    379                 return false;
    380             }
    381             if (tagService.isNdef(mTag.getServiceHandle())) {
    382                 int errorCode = tagService.ndefMakeReadOnly(mTag.getServiceHandle());
    383                 switch (errorCode) {
    384                     case ErrorCodes.SUCCESS:
    385                         return true;
    386                     case ErrorCodes.ERROR_IO:
    387                         throw new IOException();
    388                     case ErrorCodes.ERROR_INVALID_PARAM:
    389                         return false;
    390                     default:
    391                         // Should not happen
    392                         throw new IOException();
    393                 }
    394            }
    395            else {
    396                throw new IOException("Tag is not ndef");
    397            }
    398         } catch (RemoteException e) {
    399             Log.e(TAG, "NFC service dead", e);
    400             return false;
    401         }
    402     }
    403 }
    404