content: /*
   * 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.os.Parcel;
  import android.os.Parcelable;
  
  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(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);
      }
  }