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; 18 19 import java.nio.ByteBuffer; 20 import java.util.Arrays; 21 22 import android.os.Parcel; 23 import android.os.Parcelable; 24 25 26 /** 27 * Represents an immutable NDEF Message. 28 * <p> 29 * NDEF (NFC Data Exchange Format) is a light-weight binary format, 30 * used to encapsulate typed data. It is specified by the NFC Forum, 31 * for transmission and storage with NFC, however it is transport agnostic. 32 * <p> 33 * NDEF defines messages and records. An NDEF Record contains 34 * typed data, such as MIME-type media, a URI, or a custom 35 * application payload. An NDEF Message is a container for 36 * one or more NDEF Records. 37 * <p> 38 * When an Android device receives an NDEF Message 39 * (for example by reading an NFC tag) it processes it through 40 * a dispatch mechanism to determine an activity to launch. 41 * The type of the <em>first</em> record in the message has 42 * special importance for message dispatch, so design this record 43 * carefully. 44 * <p> 45 * Use {@link #NdefMessage(byte[])} to construct an NDEF Message from 46 * binary data, or {@link #NdefMessage(NdefRecord[])} to 47 * construct from one or more {@link NdefRecord}s. 48 * <p class="note"> 49 * {@link NdefMessage} and {@link NdefRecord} implementations are 50 * always available, even on Android devices that do not have NFC hardware. 51 * <p class="note"> 52 * {@link NdefRecord}s are intended to be immutable (and thread-safe), 53 * however they may contain mutable fields. So take care not to modify 54 * mutable fields passed into constructors, or modify mutable fields 55 * obtained by getter methods, unless such modification is explicitly 56 * marked as safe. 57 * 58 * @see NfcAdapter#ACTION_NDEF_DISCOVERED 59 * @see NdefRecord 60 */ 61 public final class NdefMessage implements Parcelable { 62 private final NdefRecord[] mRecords; 63 64 /** 65 * Construct an NDEF Message by parsing raw bytes.<p> 66 * Strict validation of the NDEF binary structure is performed: 67 * there must be at least one record, every record flag must 68 * be correct, and the total length of the message must match 69 * the length of the input data.<p> 70 * This parser can handle chunked records, and converts them 71 * into logical {@link NdefRecord}s within the message.<p> 72 * Once the input data has been parsed to one or more logical 73 * records, basic validation of the tnf, type, id, and payload fields 74 * of each record is performed, as per the documentation on 75 * on {@link NdefRecord#NdefRecord(short, byte[], byte[], byte[])}<p> 76 * If either strict validation of the binary format fails, or 77 * basic validation during record construction fails, a 78 * {@link FormatException} is thrown<p> 79 * Deep inspection of the type, id and payload fields of 80 * each record is not performed, so it is possible to parse input 81 * that has a valid binary format and confirms to the basic 82 * validation requirements of 83 * {@link NdefRecord#NdefRecord(short, byte[], byte[], byte[])}, 84 * but fails more strict requirements as specified by the 85 * NFC Forum. 86 * 87 * <p class="note"> 88 * It is safe to re-use the data byte array after construction: 89 * this constructor will make an internal copy of all necessary fields. 90 * 91 * @param data raw bytes to parse 92 * @throws FormatException if the data cannot be parsed 93 */ 94 public NdefMessage(byte[] data) throws FormatException { 95 if (data == null) throw new NullPointerException("data is null"); 96 ByteBuffer buffer = ByteBuffer.wrap(data); 97 98 mRecords = NdefRecord.parse(buffer, false); 99 100 if (buffer.remaining() > 0) { 101 throw new FormatException("trailing data"); 102 } 103 } 104 105 /** 106 * Construct an NDEF Message from one or more NDEF Records. 107 * 108 * @param record first record (mandatory) 109 * @param records additional records (optional) 110 */ 111 public NdefMessage(NdefRecord record, NdefRecord ... records) { 112 // validate 113 if (record == null) throw new NullPointerException("record cannot be null"); 114 115 for (NdefRecord r : records) { 116 if (r == null) { 117 throw new NullPointerException("record cannot be null"); 118 } 119 } 120 121 mRecords = new NdefRecord[1 + records.length]; 122 mRecords[0] = record; 123 System.arraycopy(records, 0, mRecords, 1, records.length); 124 } 125 126 /** 127 * Construct an NDEF Message from one or more NDEF Records. 128 * 129 * @param records one or more records 130 */ 131 public NdefMessage(NdefRecord[] records) { 132 // validate 133 if (records.length < 1) { 134 throw new IllegalArgumentException("must have at least one record"); 135 } 136 for (NdefRecord r : records) { 137 if (r == null) { 138 throw new NullPointerException("records cannot contain null"); 139 } 140 } 141 142 mRecords = records; 143 } 144 145 /** 146 * Get the NDEF Records inside this NDEF Message.<p> 147 * An {@link NdefMessage} always has one or more NDEF Records: so the 148 * following code to retrieve the first record is always safe 149 * (no need to check for null or array length >= 1): 150 * <pre> 151 * NdefRecord firstRecord = ndefMessage.getRecords()[0]; 152 * </pre> 153 * 154 * @return array of one or more NDEF records. 155 */ 156 public NdefRecord[] getRecords() { 157 return mRecords; 158 } 159 160 /** 161 * Return the length of this NDEF Message if it is written to a byte array 162 * with {@link #toByteArray}.<p> 163 * An NDEF Message can be formatted to bytes in different ways 164 * depending on chunking, SR, and ID flags, so the length returned 165 * by this method may not be equal to the length of the original 166 * byte array used to construct this NDEF Message. However it will 167 * always be equal to the length of the byte array produced by 168 * {@link #toByteArray}. 169 * 170 * @return length of this NDEF Message when written to bytes with {@link #toByteArray} 171 * @see #toByteArray 172 */ 173 public int getByteArrayLength() { 174 int length = 0; 175 for (NdefRecord r : mRecords) { 176 length += r.getByteLength(); 177 } 178 return length; 179 } 180 181 /** 182 * Return this NDEF Message as raw bytes.<p> 183 * The NDEF Message is formatted as per the NDEF 1.0 specification, 184 * and the byte array is suitable for network transmission or storage 185 * in an NFC Forum NDEF compatible tag.<p> 186 * This method will not chunk any records, and will always use the 187 * short record (SR) format and omit the identifier field when possible. 188 * 189 * @return NDEF Message in binary format 190 * @see getByteArrayLength 191 */ 192 public byte[] toByteArray() { 193 int length = getByteArrayLength(); 194 ByteBuffer buffer = ByteBuffer.allocate(length); 195 196 for (int i=0; i<mRecords.length; i++) { 197 boolean mb = (i == 0); // first record 198 boolean me = (i == mRecords.length - 1); // last record 199 mRecords[i].writeToByteBuffer(buffer, mb, me); 200 } 201 202 return buffer.array(); 203 } 204 205 @Override 206 public int describeContents() { 207 return 0; 208 } 209 210 @Override 211 public void writeToParcel(Parcel dest, int flags) { 212 dest.writeInt(mRecords.length); 213 dest.writeTypedArray(mRecords, flags); 214 } 215 216 public static final Parcelable.Creator<NdefMessage> CREATOR = 217 new Parcelable.Creator<NdefMessage>() { 218 @Override 219 public NdefMessage createFromParcel(Parcel in) { 220 int recordsLength = in.readInt(); 221 NdefRecord[] records = new NdefRecord[recordsLength]; 222 in.readTypedArray(records, NdefRecord.CREATOR); 223 return new NdefMessage(records); 224 } 225 @Override 226 public NdefMessage[] newArray(int size) { 227 return new NdefMessage[size]; 228 } 229 }; 230 231 @Override 232 public int hashCode() { 233 return Arrays.hashCode(mRecords); 234 } 235 236 /** 237 * Returns true if the specified NDEF Message contains 238 * identical NDEF Records. 239 */ 240 @Override 241 public boolean equals(Object obj) { 242 if (this == obj) return true; 243 if (obj == null) return false; 244 if (getClass() != obj.getClass()) return false; 245 NdefMessage other = (NdefMessage) obj; 246 return Arrays.equals(mRecords, other.mRecords); 247 } 248 249 @Override 250 public String toString() { 251 return "NdefMessage " + Arrays.toString(mRecords); 252 } 253 }