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