content: /*
   * Copyright (C) 2012 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 com.android.gallery3d.exif;
  
  import android.util.Log;
  
  import java.io.UnsupportedEncodingException;
  import java.nio.ByteOrder;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  
  /**
   * This class stores the EXIF header in IFDs according to the JPEG
   * specification. It is the result produced by {@link ExifReader}.
   *
   * @see ExifReader
   * @see IfdData
   */
  class ExifData {
      private static final String TAG = "ExifData";
      private static final byte[] USER_COMMENT_ASCII = {
              0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00
      };
      private static final byte[] USER_COMMENT_JIS = {
              0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00
      };
      private static final byte[] USER_COMMENT_UNICODE = {
              0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00
      };
  
      private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT];
      private byte[] mThumbnail;
      private ArrayList<byte[]> mStripBytes = new ArrayList<byte[]>();
      private final ByteOrder mByteOrder;
  
      ExifData(ByteOrder order) {
          mByteOrder = order;
      }
  
      /**
       * Gets the compressed thumbnail. Returns null if there is no compressed
       * thumbnail.
       *
       * @see #hasCompressedThumbnail()
       */
      protected byte[] getCompressedThumbnail() {
          return mThumbnail;
      }
  
      /**
       * Sets the compressed thumbnail.
       */
      protected void setCompressedThumbnail(byte[] thumbnail) {
          mThumbnail = thumbnail;
      }
  
      /**
       * Returns true it this header contains a compressed thumbnail.
       */
      protected boolean hasCompressedThumbnail() {
          return mThumbnail != null;
      }
  
      /**
       * Adds an uncompressed strip.
       */
      protected void setStripBytes(int index, byte[] strip) {
          if (index < mStripBytes.size()) {
              mStripBytes.set(index, strip);
          } else {
              for (int i = mStripBytes.size(); i < index; i++) {
                  mStripBytes.add(null);
              }
              mStripBytes.add(strip);
          }
      }
  
      /**
       * Gets the strip count.
       */
      protected int getStripCount() {
          return mStripBytes.size();
      }
  
      /**
       * Gets the strip at the specified index.
       *
       * @exceptions #IndexOutOfBoundException
       */
      protected byte[] getStrip(int index) {
          return mStripBytes.get(index);
      }
  
      /**
       * Returns true if this header contains uncompressed strip.
       */
      protected boolean hasUncompressedStrip() {
          return mStripBytes.size() != 0;
      }
  
      /**
       * Gets the byte order.
       */
      protected ByteOrder getByteOrder() {
          return mByteOrder;
      }
  
      /**
       * Returns the {@link IfdData} object corresponding to a given IFD if it
       * exists or null.
       */
      protected IfdData getIfdData(int ifdId) {
          if (ExifTag.isValidIfd(ifdId)) {
              return mIfdDatas[ifdId];
          }
          return null;
      }
  
      /**
       * Adds IFD data. If IFD data of the same type already exists, it will be
       * replaced by the new data.
       */
      protected void addIfdData(IfdData data) {
          mIfdDatas[data.getId()] = data;
      }
  
      /**
       * Returns the {@link IfdData} object corresponding to a given IFD or
       * generates one if none exist.
       */
      protected IfdData getOrCreateIfdData(int ifdId) {
          IfdData ifdData = mIfdDatas[ifdId];
          if (ifdData == null) {
              ifdData = new IfdData(ifdId);
              mIfdDatas[ifdId] = ifdData;
          }
          return ifdData;
      }
  
      /**
       * Returns the tag with a given TID in the given IFD if the tag exists.
       * Otherwise returns null.
       */
      protected ExifTag getTag(short tag, int ifd) {
          IfdData ifdData = mIfdDatas[ifd];
          return (ifdData == null) ? null : ifdData.getTag(tag);
      }
  
      /**
       * Adds the given ExifTag to its default IFD and returns an existing ExifTag
       * with the same TID or null if none exist.
       */
      protected ExifTag addTag(ExifTag tag) {
          if (tag != null) {
              int ifd = tag.getIfd();
              return addTag(tag, ifd);
          }
          return null;
      }
  
      /**
       * Adds the given ExifTag to the given IFD and returns an existing ExifTag
       * with the same TID or null if none exist.
       */
      protected ExifTag addTag(ExifTag tag, int ifdId) {
          if (tag != null && ExifTag.isValidIfd(ifdId)) {
              IfdData ifdData = getOrCreateIfdData(ifdId);
              return ifdData.setTag(tag);
          }
          return null;
      }
  
      protected void clearThumbnailAndStrips() {
          mThumbnail = null;
          mStripBytes.clear();
      }
  
      /**
       * Removes the thumbnail and its related tags. IFD1 will be removed.
       */
      protected void removeThumbnailData() {
          clearThumbnailAndStrips();
          mIfdDatas[IfdId.TYPE_IFD_1] = null;
      }
  
      /**
       * Removes the tag with a given TID and IFD.
       */
      protected void removeTag(short tagId, int ifdId) {
          IfdData ifdData = mIfdDatas[ifdId];
          if (ifdData == null) {
              return;
          }
          ifdData.removeTag(tagId);
      }
  
      /**
       * Decodes the user comment tag into string as specified in the EXIF
       * standard. Returns null if decoding failed.
       */
      protected String getUserComment() {
          IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_0];
          if (ifdData == null) {
              return null;
          }
          ExifTag tag = ifdData.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT));
          if (tag == null) {
              return null;
          }
          if (tag.getComponentCount() < 8) {
              return null;
          }
  
          byte[] buf = new byte[tag.getComponentCount()];
          tag.getBytes(buf);
  
          byte[] code = new byte[8];
          System.arraycopy(buf, 0, code, 0, 8);
  
          try {
              if (Arrays.equals(code, USER_COMMENT_ASCII)) {
                  return new String(buf, 8, buf.length - 8, "US-ASCII");
              } else if (Arrays.equals(code, USER_COMMENT_JIS)) {
                  return new String(buf, 8, buf.length - 8, "EUC-JP");
              } else if (Arrays.equals(code, USER_COMMENT_UNICODE)) {
                  return new String(buf, 8, buf.length - 8, "UTF-16");
              } else {
                  return null;
              }
          } catch (UnsupportedEncodingException e) {
              Log.w(TAG, "Failed to decode the user comment");
              return null;
          }
      }
  
      /**
       * Returns a list of all {@link ExifTag}s in the ExifData or null if there
       * are none.
       */
      protected List<ExifTag> getAllTags() {
          ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
          for (IfdData d : mIfdDatas) {
              if (d != null) {
                  ExifTag[] tags = d.getAllTags();
                  if (tags != null) {
                      for (ExifTag t : tags) {
                          ret.add(t);
                      }
                  }
              }
          }
          if (ret.size() == 0) {
              return null;
          }
          return ret;
      }
  
      /**
       * Returns a list of all {@link ExifTag}s in a given IFD or null if there
       * are none.
       */
      protected List<ExifTag> getAllTagsForIfd(int ifd) {
          IfdData d = mIfdDatas[ifd];
          if (d == null) {
              return null;
          }
          ExifTag[] tags = d.getAllTags();
          if (tags == null) {
              return null;
          }
          ArrayList<ExifTag> ret = new ArrayList<ExifTag>(tags.length);
          for (ExifTag t : tags) {
              ret.add(t);
          }
          if (ret.size() == 0) {
              return null;
          }
          return ret;
      }
  
      /**
       * Returns a list of all {@link ExifTag}s with a given TID or null if there
       * are none.
       */
      protected List<ExifTag> getAllTagsForTagId(short tag) {
          ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
          for (IfdData d : mIfdDatas) {
              if (d != null) {
                  ExifTag t = d.getTag(tag);
                  if (t != null) {
                      ret.add(t);
                  }
              }
          }
          if (ret.size() == 0) {
              return null;
          }
          return ret;
      }
  
      @Override
      public boolean equals(Object obj) {
          if (this == obj) {
              return true;
          }
          if (obj == null) {
              return false;
          }
          if (obj instanceof ExifData) {
              ExifData data = (ExifData) obj;
              if (data.mByteOrder != mByteOrder ||
                      data.mStripBytes.size() != mStripBytes.size() ||
                      !Arrays.equals(data.mThumbnail, mThumbnail)) {
                  return false;
              }
              for (int i = 0; i < mStripBytes.size(); i++) {
                  if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) {
                      return false;
                  }
              }
              for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
                  IfdData ifd1 = data.getIfdData(i);
                  IfdData ifd2 = getIfdData(i);
                  if (ifd1 != ifd2 && ifd1 != null && !ifd1.equals(ifd2)) {
                      return false;
                  }
              }
              return true;
          }
          return false;
      }
  
  }
  