|  | /* | 
|  | * 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 java.nio.ByteBuffer; | 
|  | import java.util.Arrays; | 
|  |  | 
|  | import android.os.Parcel; | 
|  | import android.os.Parcelable; | 
|  |  | 
|  |  | 
|  | /** | 
|  | * 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 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(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); | 
|  | } | 
|  | } |