• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ExifData(ByteOrder order)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      */
getCompressedThumbnail()63     protected byte[] getCompressedThumbnail() {
64         return mThumbnail;
65     }
66 
67     /**
68      * Sets the compressed thumbnail.
69      */
setCompressedThumbnail(byte[] thumbnail)70     protected void setCompressedThumbnail(byte[] thumbnail) {
71         mThumbnail = thumbnail;
72     }
73 
74     /**
75      * Returns true it this header contains a compressed thumbnail.
76      */
hasCompressedThumbnail()77     protected boolean hasCompressedThumbnail() {
78         return mThumbnail != null;
79     }
80 
81     /**
82      * Adds an uncompressed strip.
83      */
setStripBytes(int index, byte[] strip)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      */
getStripCount()98     protected int getStripCount() {
99         return mStripBytes.size();
100     }
101 
102     /**
103      * Gets the strip at the specified index.
104      *
105      * @exceptions #IndexOutOfBoundException
106      */
getStrip(int index)107     protected byte[] getStrip(int index) {
108         return mStripBytes.get(index);
109     }
110 
111     /**
112      * Returns true if this header contains uncompressed strip.
113      */
hasUncompressedStrip()114     protected boolean hasUncompressedStrip() {
115         return mStripBytes.size() != 0;
116     }
117 
118     /**
119      * Gets the byte order.
120      */
getByteOrder()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      */
getIfdData(int ifdId)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      */
addIfdData(IfdData data)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      */
getOrCreateIfdData(int ifdId)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      */
getTag(short tag, int ifd)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      */
addTag(ExifTag tag)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      */
addTag(ExifTag tag, int ifdId)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 
clearThumbnailAndStrips()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      */
removeThumbnailData()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      */
removeTag(short tagId, int ifdId)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      */
getUserComment()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      */
getAllTags()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      */
getAllTagsForIfd(int ifd)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      */
getAllTagsForTagId(short tag)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
equals(Object obj)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