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