Home | History | Annotate | Download | only in exif
      1 /*
      2  * Copyright (C) 2012 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 com.android.mms.exif;
     18 
     19 import com.android.mms.LogTag;
     20 
     21 import android.util.Log;
     22 
     23 import java.io.UnsupportedEncodingException;
     24 import java.nio.ByteOrder;
     25 import java.util.ArrayList;
     26 import java.util.Arrays;
     27 import java.util.List;
     28 
     29 /**
     30  * This class stores the EXIF header in IFDs according to the JPEG
     31  * specification. It is the result produced by {@link ExifReader}.
     32  *
     33  * @see ExifReader
     34  * @see IfdData
     35  */
     36 class ExifData {
     37     private static final String TAG = LogTag.TAG;
     38     private static final byte[] USER_COMMENT_ASCII = {
     39             0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00
     40     };
     41     private static final byte[] USER_COMMENT_JIS = {
     42             0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00
     43     };
     44     private static final byte[] USER_COMMENT_UNICODE = {
     45             0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00
     46     };
     47 
     48     private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT];
     49     private byte[] mThumbnail;
     50     private final ArrayList<byte[]> mStripBytes = new ArrayList<byte[]>();
     51     private final ByteOrder mByteOrder;
     52 
     53     ExifData(ByteOrder order) {
     54         mByteOrder = order;
     55     }
     56 
     57     /**
     58      * Gets the compressed thumbnail. Returns null if there is no compressed
     59      * thumbnail.
     60      *
     61      * @see #hasCompressedThumbnail()
     62      */
     63     protected byte[] getCompressedThumbnail() {
     64         return mThumbnail;
     65     }
     66 
     67     /**
     68      * Sets the compressed thumbnail.
     69      */
     70     protected void setCompressedThumbnail(byte[] thumbnail) {
     71         mThumbnail = thumbnail;
     72     }
     73 
     74     /**
     75      * Returns true it this header contains a compressed thumbnail.
     76      */
     77     protected boolean hasCompressedThumbnail() {
     78         return mThumbnail != null;
     79     }
     80 
     81     /**
     82      * Adds an uncompressed strip.
     83      */
     84     protected void setStripBytes(int index, byte[] strip) {
     85         if (index < mStripBytes.size()) {
     86             mStripBytes.set(index, strip);
     87         } else {
     88             for (int i = mStripBytes.size(); i < index; i++) {
     89                 mStripBytes.add(null);
     90             }
     91             mStripBytes.add(strip);
     92         }
     93     }
     94 
     95     /**
     96      * Gets the strip count.
     97      */
     98     protected int getStripCount() {
     99         return mStripBytes.size();
    100     }
    101 
    102     /**
    103      * Gets the strip at the specified index.
    104      *
    105      * @exceptions #IndexOutOfBoundException
    106      */
    107     protected byte[] getStrip(int index) {
    108         return mStripBytes.get(index);
    109     }
    110 
    111     /**
    112      * Returns true if this header contains uncompressed strip.
    113      */
    114     protected boolean hasUncompressedStrip() {
    115         return mStripBytes.size() != 0;
    116     }
    117 
    118     /**
    119      * Gets the byte order.
    120      */
    121     protected ByteOrder getByteOrder() {
    122         return mByteOrder;
    123     }
    124 
    125     /**
    126      * Returns the {@link IfdData} object corresponding to a given IFD if it
    127      * exists or null.
    128      */
    129     protected IfdData getIfdData(int ifdId) {
    130         if (ExifTag.isValidIfd(ifdId)) {
    131             return mIfdDatas[ifdId];
    132         }
    133         return null;
    134     }
    135 
    136     /**
    137      * Adds IFD data. If IFD data of the same type already exists, it will be
    138      * replaced by the new data.
    139      */
    140     protected void addIfdData(IfdData data) {
    141         mIfdDatas[data.getId()] = data;
    142     }
    143 
    144     /**
    145      * Returns the {@link IfdData} object corresponding to a given IFD or
    146      * generates one if none exist.
    147      */
    148     protected IfdData getOrCreateIfdData(int ifdId) {
    149         IfdData ifdData = mIfdDatas[ifdId];
    150         if (ifdData == null) {
    151             ifdData = new IfdData(ifdId);
    152             mIfdDatas[ifdId] = ifdData;
    153         }
    154         return ifdData;
    155     }
    156 
    157     /**
    158      * Returns the tag with a given TID in the given IFD if the tag exists.
    159      * Otherwise returns null.
    160      */
    161     protected ExifTag getTag(short tag, int ifd) {
    162         IfdData ifdData = mIfdDatas[ifd];
    163         return (ifdData == null) ? null : ifdData.getTag(tag);
    164     }
    165 
    166     /**
    167      * Adds the given ExifTag to its default IFD and returns an existing ExifTag
    168      * with the same TID or null if none exist.
    169      */
    170     protected ExifTag addTag(ExifTag tag) {
    171         if (tag != null) {
    172             int ifd = tag.getIfd();
    173             return addTag(tag, ifd);
    174         }
    175         return null;
    176     }
    177 
    178     /**
    179      * Adds the given ExifTag to the given IFD and returns an existing ExifTag
    180      * with the same TID or null if none exist.
    181      */
    182     protected ExifTag addTag(ExifTag tag, int ifdId) {
    183         if (tag != null && ExifTag.isValidIfd(ifdId)) {
    184             IfdData ifdData = getOrCreateIfdData(ifdId);
    185             return ifdData.setTag(tag);
    186         }
    187         return null;
    188     }
    189 
    190     protected void clearThumbnailAndStrips() {
    191         mThumbnail = null;
    192         mStripBytes.clear();
    193     }
    194 
    195     /**
    196      * Removes the thumbnail and its related tags. IFD1 will be removed.
    197      */
    198     protected void removeThumbnailData() {
    199         clearThumbnailAndStrips();
    200         mIfdDatas[IfdId.TYPE_IFD_1] = null;
    201     }
    202 
    203     /**
    204      * Removes the tag with a given TID and IFD.
    205      */
    206     protected void removeTag(short tagId, int ifdId) {
    207         IfdData ifdData = mIfdDatas[ifdId];
    208         if (ifdData == null) {
    209             return;
    210         }
    211         ifdData.removeTag(tagId);
    212     }
    213 
    214     /**
    215      * Decodes the user comment tag into string as specified in the EXIF
    216      * standard. Returns null if decoding failed.
    217      */
    218     protected String getUserComment() {
    219         IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_0];
    220         if (ifdData == null) {
    221             return null;
    222         }
    223         ExifTag tag = ifdData.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT));
    224         if (tag == null) {
    225             return null;
    226         }
    227         if (tag.getComponentCount() < 8) {
    228             return null;
    229         }
    230 
    231         byte[] buf = new byte[tag.getComponentCount()];
    232         tag.getBytes(buf);
    233 
    234         byte[] code = new byte[8];
    235         System.arraycopy(buf, 0, code, 0, 8);
    236 
    237         try {
    238             if (Arrays.equals(code, USER_COMMENT_ASCII)) {
    239                 return new String(buf, 8, buf.length - 8, "US-ASCII");
    240             } else if (Arrays.equals(code, USER_COMMENT_JIS)) {
    241                 return new String(buf, 8, buf.length - 8, "EUC-JP");
    242             } else if (Arrays.equals(code, USER_COMMENT_UNICODE)) {
    243                 return new String(buf, 8, buf.length - 8, "UTF-16");
    244             } else {
    245                 return null;
    246             }
    247         } catch (UnsupportedEncodingException e) {
    248             Log.w(TAG, "Failed to decode the user comment");
    249             return null;
    250         }
    251     }
    252 
    253     /**
    254      * Returns a list of all {@link ExifTag}s in the ExifData or null if there
    255      * are none.
    256      */
    257     protected List<ExifTag> getAllTags() {
    258         ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
    259         for (IfdData d : mIfdDatas) {
    260             if (d != null) {
    261                 ExifTag[] tags = d.getAllTags();
    262                 if (tags != null) {
    263                     for (ExifTag t : tags) {
    264                         ret.add(t);
    265                     }
    266                 }
    267             }
    268         }
    269         if (ret.size() == 0) {
    270             return null;
    271         }
    272         return ret;
    273     }
    274 
    275     /**
    276      * Returns a list of all {@link ExifTag}s in a given IFD or null if there
    277      * are none.
    278      */
    279     protected List<ExifTag> getAllTagsForIfd(int ifd) {
    280         IfdData d = mIfdDatas[ifd];
    281         if (d == null) {
    282             return null;
    283         }
    284         ExifTag[] tags = d.getAllTags();
    285         if (tags == null) {
    286             return null;
    287         }
    288         ArrayList<ExifTag> ret = new ArrayList<ExifTag>(tags.length);
    289         for (ExifTag t : tags) {
    290             ret.add(t);
    291         }
    292         if (ret.size() == 0) {
    293             return null;
    294         }
    295         return ret;
    296     }
    297 
    298     /**
    299      * Returns a list of all {@link ExifTag}s with a given TID or null if there
    300      * are none.
    301      */
    302     protected List<ExifTag> getAllTagsForTagId(short tag) {
    303         ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
    304         for (IfdData d : mIfdDatas) {
    305             if (d != null) {
    306                 ExifTag t = d.getTag(tag);
    307                 if (t != null) {
    308                     ret.add(t);
    309                 }
    310             }
    311         }
    312         if (ret.size() == 0) {
    313             return null;
    314         }
    315         return ret;
    316     }
    317 
    318     @Override
    319     public boolean equals(Object obj) {
    320         if (this == obj) {
    321             return true;
    322         }
    323         if (obj == null) {
    324             return false;
    325         }
    326         if (obj instanceof ExifData) {
    327             ExifData data = (ExifData) obj;
    328             if (data.mByteOrder != mByteOrder ||
    329                     data.mStripBytes.size() != mStripBytes.size() ||
    330                     !Arrays.equals(data.mThumbnail, mThumbnail)) {
    331                 return false;
    332             }
    333             for (int i = 0; i < mStripBytes.size(); i++) {
    334                 if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) {
    335                     return false;
    336                 }
    337             }
    338             for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
    339                 IfdData ifd1 = data.getIfdData(i);
    340                 IfdData ifd2 = getIfdData(i);
    341                 if (ifd1 != ifd2 && ifd1 != null && !ifd1.equals(ifd2)) {
    342                     return false;
    343                 }
    344             }
    345             return true;
    346         }
    347         return false;
    348     }
    349 
    350 }
    351