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