| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.nfc; |
| |
| import android.annotation.Nullable; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.util.proto.ProtoOutputStream; |
| |
| import java.nio.ByteBuffer; |
| import java.util.Arrays; |
| |
| /** |
| * Represents an immutable NDEF Message. |
| * <p> |
| * NDEF (NFC Data Exchange Format) is a light-weight binary format, |
| * used to encapsulate typed data. It is specified by the NFC Forum, |
| * for transmission and storage with NFC, however it is transport agnostic. |
| * <p> |
| * NDEF defines messages and records. An NDEF Record contains |
| * typed data, such as MIME-type media, a URI, or a custom |
| * application payload. An NDEF Message is a container for |
| * one or more NDEF Records. |
| * <p> |
| * When an Android device receives an NDEF Message |
| * (for example by reading an NFC tag) it processes it through |
| * a dispatch mechanism to determine an activity to launch. |
| * The type of the <em>first</em> record in the message has |
| * special importance for message dispatch, so design this record |
| * carefully. |
| * <p> |
| * Use {@link #NdefMessage(byte[])} to construct an NDEF Message from |
| * binary data, or {@link #NdefMessage(NdefRecord[])} to |
| * construct from one or more {@link NdefRecord}s. |
| * <p class="note"> |
| * {@link NdefMessage} and {@link NdefRecord} implementations are |
| * always available, even on Android devices that do not have NFC hardware. |
| * <p class="note"> |
| * {@link NdefRecord}s are intended to be immutable (and thread-safe), |
| * however they may contain mutable fields. So take care not to modify |
| * mutable fields passed into constructors, or modify mutable fields |
| * obtained by getter methods, unless such modification is explicitly |
| * marked as safe. |
| * |
| * @see NfcAdapter#ACTION_NDEF_DISCOVERED |
| * @see NdefRecord |
| */ |
| public final class NdefMessage implements Parcelable { |
| private final NdefRecord[] mRecords; |
| |
| /** |
| * Construct an NDEF Message by parsing raw bytes.<p> |
| * Strict validation of the NDEF binary structure is performed: |
| * there must be at least one record, every record flag must |
| * be correct, and the total length of the message must match |
| * the length of the input data.<p> |
| * This parser can handle chunked records, and converts them |
| * into logical {@link NdefRecord}s within the message.<p> |
| * Once the input data has been parsed to one or more logical |
| * records, basic validation of the tnf, type, id, and payload fields |
| * of each record is performed, as per the documentation on |
| * on {@link NdefRecord#NdefRecord(short, byte[], byte[], byte[])}<p> |
| * If either strict validation of the binary format fails, or |
| * basic validation during record construction fails, a |
| * {@link FormatException} is thrown<p> |
| * Deep inspection of the type, id and payload fields of |
| * each record is not performed, so it is possible to parse input |
| * that has a valid binary format and confirms to the basic |
| * validation requirements of |
| * {@link NdefRecord#NdefRecord(short, byte[], byte[], byte[])}, |
| * but fails more strict requirements as specified by the |
| * NFC Forum. |
| * |
| * <p class="note"> |
| * It is safe to re-use the data byte array after construction: |
| * this constructor will make an internal copy of all necessary fields. |
| * |
| * @param data raw bytes to parse |
| * @throws FormatException if the data cannot be parsed |
| */ |
| public NdefMessage(byte[] data) throws FormatException { |
| if (data == null) throw new NullPointerException("data is null"); |
| ByteBuffer buffer = ByteBuffer.wrap(data); |
| |
| mRecords = NdefRecord.parse(buffer, false); |
| |
| if (buffer.remaining() > 0) { |
| throw new FormatException("trailing data"); |
| } |
| } |
| |
| /** |
| * Construct an NDEF Message from one or more NDEF Records. |
| * |
| * @param record first record (mandatory) |
| * @param records additional records (optional) |
| */ |
| public NdefMessage(NdefRecord record, NdefRecord ... records) { |
| // validate |
| if (record == null) throw new NullPointerException("record cannot be null"); |
| |
| for (NdefRecord r : records) { |
| if (r == null) { |
| throw new NullPointerException("record cannot be null"); |
| } |
| } |
| |
| mRecords = new NdefRecord[1 + records.length]; |
| mRecords[0] = record; |
| System.arraycopy(records, 0, mRecords, 1, records.length); |
| } |
| |
| /** |
| * Construct an NDEF Message from one or more NDEF Records. |
| * |
| * @param records one or more records |
| */ |
| public NdefMessage(NdefRecord[] records) { |
| // validate |
| if (records.length < 1) { |
| throw new IllegalArgumentException("must have at least one record"); |
| } |
| for (NdefRecord r : records) { |
| if (r == null) { |
| throw new NullPointerException("records cannot contain null"); |
| } |
| } |
| |
| mRecords = records; |
| } |
| |
| /** |
| * Get the NDEF Records inside this NDEF Message.<p> |
| * An {@link NdefMessage} always has one or more NDEF Records: so the |
| * following code to retrieve the first record is always safe |
| * (no need to check for null or array length >= 1): |
| * <pre> |
| * NdefRecord firstRecord = ndefMessage.getRecords()[0]; |
| * </pre> |
| * |
| * @return array of one or more NDEF records. |
| */ |
| public NdefRecord[] getRecords() { |
| return mRecords; |
| } |
| |
| /** |
| * Return the length of this NDEF Message if it is written to a byte array |
| * with {@link #toByteArray}.<p> |
| * An NDEF Message can be formatted to bytes in different ways |
| * depending on chunking, SR, and ID flags, so the length returned |
| * by this method may not be equal to the length of the original |
| * byte array used to construct this NDEF Message. However it will |
| * always be equal to the length of the byte array produced by |
| * {@link #toByteArray}. |
| * |
| * @return length of this NDEF Message when written to bytes with {@link #toByteArray} |
| * @see #toByteArray |
| */ |
| public int getByteArrayLength() { |
| int length = 0; |
| for (NdefRecord r : mRecords) { |
| length += r.getByteLength(); |
| } |
| return length; |
| } |
| |
| /** |
| * Return this NDEF Message as raw bytes.<p> |
| * The NDEF Message is formatted as per the NDEF 1.0 specification, |
| * and the byte array is suitable for network transmission or storage |
| * in an NFC Forum NDEF compatible tag.<p> |
| * This method will not chunk any records, and will always use the |
| * short record (SR) format and omit the identifier field when possible. |
| * |
| * @return NDEF Message in binary format |
| * @see #getByteArrayLength() |
| */ |
| public byte[] toByteArray() { |
| int length = getByteArrayLength(); |
| ByteBuffer buffer = ByteBuffer.allocate(length); |
| |
| for (int i=0; i<mRecords.length; i++) { |
| boolean mb = (i == 0); // first record |
| boolean me = (i == mRecords.length - 1); // last record |
| mRecords[i].writeToByteBuffer(buffer, mb, me); |
| } |
| |
| return buffer.array(); |
| } |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| |
| @Override |
| public void writeToParcel(Parcel dest, int flags) { |
| dest.writeInt(mRecords.length); |
| dest.writeTypedArray(mRecords, flags); |
| } |
| |
| public static final @android.annotation.NonNull Parcelable.Creator<NdefMessage> CREATOR = |
| new Parcelable.Creator<NdefMessage>() { |
| @Override |
| public NdefMessage createFromParcel(Parcel in) { |
| int recordsLength = in.readInt(); |
| NdefRecord[] records = new NdefRecord[recordsLength]; |
| in.readTypedArray(records, NdefRecord.CREATOR); |
| return new NdefMessage(records); |
| } |
| @Override |
| public NdefMessage[] newArray(int size) { |
| return new NdefMessage[size]; |
| } |
| }; |
| |
| @Override |
| public int hashCode() { |
| return Arrays.hashCode(mRecords); |
| } |
| |
| /** |
| * Returns true if the specified NDEF Message contains |
| * identical NDEF Records. |
| */ |
| @Override |
| public boolean equals(@Nullable Object obj) { |
| if (this == obj) return true; |
| if (obj == null) return false; |
| if (getClass() != obj.getClass()) return false; |
| NdefMessage other = (NdefMessage) obj; |
| return Arrays.equals(mRecords, other.mRecords); |
| } |
| |
| @Override |
| public String toString() { |
| return "NdefMessage " + Arrays.toString(mRecords); |
| } |
| |
| /** |
| * Dump debugging information as a NdefMessageProto |
| * @hide |
| * |
| * Note: |
| * See proto definition in frameworks/base/core/proto/android/nfc/ndef.proto |
| * When writing a nested message, must call {@link ProtoOutputStream#start(long)} before and |
| * {@link ProtoOutputStream#end(long)} after. |
| * Never reuse a proto field number. When removing a field, mark it as reserved. |
| */ |
| public void dumpDebug(ProtoOutputStream proto) { |
| for (NdefRecord record : mRecords) { |
| long token = proto.start(NdefMessageProto.NDEF_RECORDS); |
| record.dumpDebug(proto); |
| proto.end(token); |
| } |
| } |
| } |