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