Ndef.java revision f003e26df96067b4b136f0859012cb7ec3ed930f
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
17package android.nfc.tech;
18
19import android.nfc.ErrorCodes;
20import android.nfc.FormatException;
21import android.nfc.INfcTag;
22import android.nfc.NdefMessage;
23import android.nfc.NfcAdapter;
24import android.nfc.Tag;
25import android.os.Bundle;
26import android.os.RemoteException;
27import android.util.Log;
28
29import java.io.IOException;
30
31/**
32 * A high-level connection to a {@link Tag} using one of the NFC type 1, 2, 3, or 4 technologies
33 * to interact with NDEF data. MiFare Classic cards that present NDEF data may also be used
34 * via this class. To determine the exact technology being used call {@link #getType()}
35 *
36 * <p>You can acquire this kind of connection with {@link #get}.
37 *
38 * <p class="note"><strong>Note:</strong>
39 * Use of this class requires the {@link android.Manifest.permission#NFC}
40 * permission.
41 */
42public final class Ndef extends BasicTagTechnology {
43    private static final String TAG = "NFC";
44
45    /** @hide */
46    public static final int NDEF_MODE_READ_ONLY = 1;
47    /** @hide */
48    public static final int NDEF_MODE_READ_WRITE = 2;
49    /** @hide */
50    public static final int NDEF_MODE_UNKNOWN = 3;
51
52    /** @hide */
53    public static final String EXTRA_NDEF_MSG = "ndefmsg";
54
55    /** @hide */
56    public static final String EXTRA_NDEF_MAXLENGTH = "ndefmaxlength";
57
58    /** @hide */
59    public static final String EXTRA_NDEF_CARDSTATE = "ndefcardstate";
60
61    /** @hide */
62    public static final String EXTRA_NDEF_TYPE = "ndeftype";
63
64    /** @hide */
65    public static final int TYPE_OTHER = -1;
66    /** @hide */
67    public static final int TYPE_1 = 1;
68    /** @hide */
69    public static final int TYPE_2 = 2;
70    /** @hide */
71    public static final int TYPE_3 = 3;
72    /** @hide */
73    public static final int TYPE_4 = 4;
74    /** @hide */
75    public static final int TYPE_MIFARE_CLASSIC = 101;
76
77    /** @hide */
78    public static final String UNKNOWN = "android.ndef.unknown";
79
80    public static final String NFC_FORUM_TYPE_1 = "org.nfcforum.ndef.type1";
81
82    public static final String NFC_FORUM_TYPE_2 = "org.nfcforum.ndef.type2";
83
84    public static final String NFC_FORUM_TYPE_3 = "org.nfcforum.ndef.type3";
85
86    public static final String NFC_FORUM_TYPE_4 = "org.nfcforum.ndef.type4";
87
88    public static final String MIFARE_CLASSIC = "com.nxp.ndef.mifareclassic";
89
90    private final int mMaxNdefSize;
91    private final int mCardState;
92    private final NdefMessage mNdefMsg;
93    private final int mNdefType;
94
95    /**
96     * Returns an instance of this tech for the given tag. If the tag doesn't support
97     * this tech type null is returned.
98     *
99     * @param tag The tag to get the tech from
100     */
101    public static Ndef get(Tag tag) {
102        if (!tag.hasTech(TagTechnology.NDEF)) return null;
103        try {
104            return new Ndef(tag);
105        } catch (RemoteException e) {
106            return null;
107        }
108    }
109
110    /**
111     * Internal constructor, to be used by NfcAdapter
112     * @hide
113     */
114    public Ndef(Tag tag) throws RemoteException {
115        super(tag, TagTechnology.NDEF);
116        Bundle extras = tag.getTechExtras(TagTechnology.NDEF);
117        if (extras != null) {
118            mMaxNdefSize = extras.getInt(EXTRA_NDEF_MAXLENGTH);
119            mCardState = extras.getInt(EXTRA_NDEF_CARDSTATE);
120            mNdefMsg = extras.getParcelable(EXTRA_NDEF_MSG);
121            mNdefType = extras.getInt(EXTRA_NDEF_TYPE);
122        } else {
123            throw new NullPointerException("NDEF tech extras are null.");
124        }
125
126    }
127
128    /**
129     * Get the primary NDEF message on this tag. This data is read at discovery time
130     * and does not require a connection.
131     */
132    public NdefMessage getCachedNdefMessage() {
133        return mNdefMsg;
134    }
135
136    /**
137     * Get NDEF tag type.
138     * <p>Returns one of {@link #NFC_FORUM_TYPE_1}, {@link #NFC_FORUM_TYPE_2},
139     * {@link #NFC_FORUM_TYPE_3}, {@link #NFC_FORUM_TYPE_4},
140     * {@link #MIFARE_CLASSIC} or another NDEF tag type that is not yet in the
141     * Android API.
142     * <p>Android devices with NFC support must always correctly enumerate
143     * NFC Forum tag types, and may optionally enumerate
144     * {@link #MIFARE_CLASSIC} since it requires proprietary technology.
145     */
146    public String getType() {
147        switch (mNdefType) {
148            case TYPE_1:
149                return NFC_FORUM_TYPE_1;
150            case TYPE_2:
151                return NFC_FORUM_TYPE_2;
152            case TYPE_3:
153                return NFC_FORUM_TYPE_3;
154            case TYPE_4:
155                return NFC_FORUM_TYPE_4;
156            case TYPE_MIFARE_CLASSIC:
157                return MIFARE_CLASSIC;
158            default:
159                return UNKNOWN;
160        }
161    }
162
163    /**
164     * Get maximum NDEF message size in bytes
165     */
166    public int getMaxSize() {
167        return mMaxNdefSize;
168    }
169
170    /**
171     * Provides a hint on whether writes are likely to succeed.
172     * <p>Requires {@link android.Manifest.permission#NFC} permission.
173     * @return true if write is likely to succeed
174     */
175    public boolean isWritable() {
176        return (mCardState == NDEF_MODE_READ_WRITE);
177    }
178
179    // Methods that require connect()
180    /**
181     * Get the primary NDEF message on this tag. This data is read actively
182     * and requires a connection.
183     */
184    public NdefMessage getNdefMessage() throws IOException, FormatException {
185        checkConnected();
186
187        try {
188            INfcTag tagService = mTag.getTagService();
189            int serviceHandle = mTag.getServiceHandle();
190            if (tagService.isNdef(serviceHandle)) {
191                NdefMessage msg = tagService.ndefRead(serviceHandle);
192                if (msg == null) {
193                    int errorCode = tagService.getLastError(serviceHandle);
194                    switch (errorCode) {
195                        case ErrorCodes.ERROR_IO:
196                            throw new IOException();
197                        case ErrorCodes.ERROR_INVALID_PARAM:
198                            throw new FormatException();
199                        default:
200                            // Should not happen
201                            throw new IOException();
202                    }
203                }
204                return msg;
205            } else {
206                return null;
207            }
208        } catch (RemoteException e) {
209            Log.e(TAG, "NFC service dead", e);
210            return null;
211        }
212    }
213
214    /**
215     * Overwrite the primary NDEF message
216     * @throws IOException
217     */
218    public void writeNdefMessage(NdefMessage msg) throws IOException, FormatException {
219        checkConnected();
220
221        try {
222            INfcTag tagService = mTag.getTagService();
223            int serviceHandle = mTag.getServiceHandle();
224            if (tagService.isNdef(serviceHandle)) {
225                int errorCode = tagService.ndefWrite(serviceHandle, msg);
226                switch (errorCode) {
227                    case ErrorCodes.SUCCESS:
228                        break;
229                    case ErrorCodes.ERROR_IO:
230                        throw new IOException();
231                    case ErrorCodes.ERROR_INVALID_PARAM:
232                        throw new FormatException();
233                    default:
234                        // Should not happen
235                        throw new IOException();
236                }
237            }
238            else {
239                throw new IOException("Tag is not ndef");
240            }
241        } catch (RemoteException e) {
242            Log.e(TAG, "NFC service dead", e);
243        }
244    }
245
246    /**
247     * Indicates whether a tag can be made read-only with
248     * {@link #makeReadOnly()}
249     */
250    public boolean canMakeReadOnly() {
251        if (mNdefType == TYPE_1 || mNdefType == TYPE_2) {
252            return true;
253        } else {
254            return false;
255        }
256    }
257
258    /**
259     * Sets the CC field to indicate this tag is read-only
260     * and permanently sets the lock bits to prevent any further NDEF
261     * modifications.
262     * This is a one-way process and can not be reverted!
263     * @throws IOException
264     */
265    public boolean makeReadOnly() throws IOException {
266        checkConnected();
267
268        try {
269            INfcTag tagService = mTag.getTagService();
270            if (tagService.isNdef(mTag.getServiceHandle())) {
271                int errorCode = tagService.ndefMakeReadOnly(mTag.getServiceHandle());
272                switch (errorCode) {
273                    case ErrorCodes.SUCCESS:
274                        return true;
275                    case ErrorCodes.ERROR_IO:
276                        throw new IOException();
277                    case ErrorCodes.ERROR_INVALID_PARAM:
278                        return false;
279                    default:
280                        // Should not happen
281                        throw new IOException();
282                }
283           }
284           else {
285               throw new IOException("Tag is not ndef");
286           }
287        } catch (RemoteException e) {
288            Log.e(TAG, "NFC service dead", e);
289            return false;
290        }
291    }
292}
293