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