ExifInterface.java revision 15ef59e081846b43660635391d536361c3253a22
1/*
2 * Copyright (C) 2007 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
17package android.media;
18
19import android.graphics.Bitmap;
20import android.graphics.BitmapFactory;
21import android.system.ErrnoException;
22import android.system.Os;
23import android.system.OsConstants;
24import android.util.Log;
25import android.util.Pair;
26
27import java.io.ByteArrayInputStream;
28import java.io.DataInputStream;
29import java.io.DataOutputStream;
30import java.io.EOFException;
31import java.io.File;
32import java.io.FileDescriptor;
33import java.io.FileInputStream;
34import java.io.FileNotFoundException;
35import java.io.FileOutputStream;
36import java.io.IOException;
37import java.io.InputStream;
38import java.io.OutputStream;
39import java.nio.ByteOrder;
40import java.nio.charset.Charset;
41import java.text.ParsePosition;
42import java.text.SimpleDateFormat;
43import java.util.Arrays;
44import java.util.Date;
45import java.util.HashMap;
46import java.util.Map;
47import java.util.TimeZone;
48import java.util.regex.Pattern;
49
50import libcore.io.IoUtils;
51import libcore.io.Streams;
52
53/**
54 * This is a class for reading and writing Exif tags in a JPEG file or a RAW image file.
55 * <p>
56 * Supported formats are: JPEG, DNG, CR2, NEF, NRW, ARW, RW2, ORF and RAF.
57 * <p>
58 * Attribute mutation is supported for JPEG image files.
59 */
60public class ExifInterface {
61    private static final String TAG = "ExifInterface";
62    private static final boolean DEBUG = false;
63
64    // The Exif tag names
65    /** Type is int. */
66    public static final String TAG_ORIENTATION = "Orientation";
67    /** Type is String. */
68    public static final String TAG_DATETIME = "DateTime";
69    /** Type is String. */
70    public static final String TAG_MAKE = "Make";
71    /** Type is String. */
72    public static final String TAG_MODEL = "Model";
73    /** Type is int. */
74    public static final String TAG_FLASH = "Flash";
75    /** Type is int. */
76    public static final String TAG_IMAGE_WIDTH = "ImageWidth";
77    /** Type is int. */
78    public static final String TAG_IMAGE_LENGTH = "ImageLength";
79    /** String. Format is "num1/denom1,num2/denom2,num3/denom3". */
80    public static final String TAG_GPS_LATITUDE = "GPSLatitude";
81    /** String. Format is "num1/denom1,num2/denom2,num3/denom3". */
82    public static final String TAG_GPS_LONGITUDE = "GPSLongitude";
83    /** Type is String. */
84    public static final String TAG_GPS_LATITUDE_REF = "GPSLatitudeRef";
85    /** Type is String. */
86    public static final String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef";
87    /** Type is String. */
88    public static final String TAG_EXPOSURE_TIME = "ExposureTime";
89    /** Type is String. */
90    public static final String TAG_APERTURE = "FNumber";
91    /** Type is String. */
92    public static final String TAG_ISO = "ISOSpeedRatings";
93    /** Type is String. */
94    public static final String TAG_DATETIME_DIGITIZED = "DateTimeDigitized";
95    /** Type is int. */
96    public static final String TAG_SUBSEC_TIME = "SubSecTime";
97    /** Type is int. */
98    public static final String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal";
99    /** Type is int. */
100    public static final String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized";
101
102    /**
103     * @hide
104     */
105    public static final String TAG_SUBSECTIME = "SubSecTime";
106
107    /**
108     * The altitude (in meters) based on the reference in TAG_GPS_ALTITUDE_REF.
109     * Type is rational.
110     */
111    public static final String TAG_GPS_ALTITUDE = "GPSAltitude";
112
113    /**
114     * 0 if the altitude is above sea level. 1 if the altitude is below sea
115     * level. Type is int.
116     */
117    public static final String TAG_GPS_ALTITUDE_REF = "GPSAltitudeRef";
118
119    /** Type is String. */
120    public static final String TAG_GPS_TIMESTAMP = "GPSTimeStamp";
121    /** Type is String. */
122    public static final String TAG_GPS_DATESTAMP = "GPSDateStamp";
123    /** Type is int. */
124    public static final String TAG_WHITE_BALANCE = "WhiteBalance";
125    /** Type is rational. */
126    public static final String TAG_FOCAL_LENGTH = "FocalLength";
127    /** Type is String. Name of GPS processing method used for location finding. */
128    public static final String TAG_GPS_PROCESSING_METHOD = "GPSProcessingMethod";
129    /** Type is double. */
130    public static final String TAG_DIGITAL_ZOOM_RATIO = "DigitalZoomRatio";
131    /** Type is double. */
132    public static final String TAG_SUBJECT_DISTANCE = "SubjectDistance";
133    /** Type is double. */
134    public static final String TAG_EXPOSURE_BIAS_VALUE = "ExposureBiasValue";
135    /** Type is int. */
136    public static final String TAG_LIGHT_SOURCE = "LightSource";
137    /** Type is int. */
138    public static final String TAG_METERING_MODE = "MeteringMode";
139    /** Type is int. */
140    public static final String TAG_EXPOSURE_PROGRAM = "ExposureProgram";
141    /** Type is int. */
142    public static final String TAG_EXPOSURE_MODE = "ExposureMode";
143
144    // Private tags used for thumbnail information.
145    private static final String TAG_HAS_THUMBNAIL = "hasThumbnail";
146    private static final String TAG_THUMBNAIL_OFFSET = "thumbnailOffset";
147    private static final String TAG_THUMBNAIL_LENGTH = "thumbnailLength";
148
149    // Constants used for the Orientation Exif tag.
150    public static final int ORIENTATION_UNDEFINED = 0;
151    public static final int ORIENTATION_NORMAL = 1;
152    public static final int ORIENTATION_FLIP_HORIZONTAL = 2;  // left right reversed mirror
153    public static final int ORIENTATION_ROTATE_180 = 3;
154    public static final int ORIENTATION_FLIP_VERTICAL = 4;  // upside down mirror
155    // flipped about top-left <--> bottom-right axis
156    public static final int ORIENTATION_TRANSPOSE = 5;
157    public static final int ORIENTATION_ROTATE_90 = 6;  // rotate 90 cw to right it
158    // flipped about top-right <--> bottom-left axis
159    public static final int ORIENTATION_TRANSVERSE = 7;
160    public static final int ORIENTATION_ROTATE_270 = 8;  // rotate 270 to right it
161
162    // Constants used for white balance
163    public static final int WHITEBALANCE_AUTO = 0;
164    public static final int WHITEBALANCE_MANUAL = 1;
165
166    private static SimpleDateFormat sFormatter;
167
168    // See Exchangeable image file format for digital still cameras: Exif version 2.2.
169    // The following values are for parsing EXIF data area. There are tag groups in EXIF data area.
170    // They are called "Image File Directory". They have multiple data formats to cover various
171    // image metadata from GPS longitude to camera model name.
172
173    // Types of Exif byte alignments (see JEITA CP-3451 page 10)
174    private static final short BYTE_ALIGN_II = 0x4949;  // II: Intel order
175    private static final short BYTE_ALIGN_MM = 0x4d4d;  // MM: Motorola order
176
177    // Formats for the value in IFD entry (See TIFF 6.0 spec Types page 15).
178    private static final int IFD_FORMAT_BYTE = 1;
179    private static final int IFD_FORMAT_STRING = 2;
180    private static final int IFD_FORMAT_USHORT = 3;
181    private static final int IFD_FORMAT_ULONG = 4;
182    private static final int IFD_FORMAT_URATIONAL = 5;
183    private static final int IFD_FORMAT_SBYTE = 6;
184    private static final int IFD_FORMAT_UNDEFINED = 7;
185    private static final int IFD_FORMAT_SSHORT = 8;
186    private static final int IFD_FORMAT_SLONG = 9;
187    private static final int IFD_FORMAT_SRATIONAL = 10;
188    private static final int IFD_FORMAT_SINGLE = 11;
189    private static final int IFD_FORMAT_DOUBLE = 12;
190    // Sizes of the components of each IFD value format
191    private static final int[] IFD_FORMAT_BYTES_PER_FORMAT = new int[] {
192            0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8
193    };
194    private static final byte[] EXIF_ASCII_PREFIX = new byte[] {
195            0x41, 0x53, 0x43, 0x49, 0x49, 0x0, 0x0, 0x0
196    };
197
198    // A class for indicating EXIF tag.
199    private static class ExifTag {
200        public final int number;
201        public final String name;
202
203        private ExifTag(String name, int number) {
204            this.name = name;
205            this.number = number;
206        }
207    }
208
209    // Primary image IFD TIFF tags (See JEITA CP-3451 Table 14. page 54).
210    private static final ExifTag[] IFD_TIFF_TAGS = new ExifTag[] {
211            new ExifTag("ImageWidth", 256),
212            new ExifTag("ImageLength", 257),
213            new ExifTag("BitsPerSample", 258),
214            new ExifTag("Compression", 259),
215            new ExifTag("PhotometricInterpretation", 262),
216            new ExifTag("ImageDescription", 270),
217            new ExifTag("Make", 271),
218            new ExifTag("Model", 272),
219            new ExifTag("StripOffsets", 273),
220            new ExifTag("Orientation", 274),
221            new ExifTag("SamplesPerPixel", 277),
222            new ExifTag("RowsPerStrip", 278),
223            new ExifTag("StripByteCounts", 279),
224            new ExifTag("XResolution", 282),
225            new ExifTag("YResolution", 283),
226            new ExifTag("PlanarConfiguration", 284),
227            new ExifTag("ResolutionUnit", 296),
228            new ExifTag("TransferFunction", 301),
229            new ExifTag("Software", 305),
230            new ExifTag("DateTime", 306),
231            new ExifTag("Artist", 315),
232            new ExifTag("WhitePoint", 318),
233            new ExifTag("PrimaryChromaticities", 319),
234            new ExifTag("JPEGInterchangeFormat", 513),
235            new ExifTag("JPEGInterchangeFormatLength", 514),
236            new ExifTag("YCbCrCoefficients", 529),
237            new ExifTag("YCbCrSubSampling", 530),
238            new ExifTag("YCbCrPositioning", 531),
239            new ExifTag("ReferenceBlackWhite", 532),
240            new ExifTag("Copyright", 33432),
241            new ExifTag("ExifIFDPointer", 34665),
242            new ExifTag("GPSInfoIFDPointer", 34853),
243    };
244    // Primary image IFD Exif Private tags (See JEITA CP-3451 Table 15. page 55).
245    private static final ExifTag[] IFD_EXIF_TAGS = new ExifTag[] {
246            new ExifTag("ExposureTime", 33434),
247            new ExifTag("FNumber", 33437),
248            new ExifTag("ExposureProgram", 34850),
249            new ExifTag("SpectralSensitivity", 34852),
250            new ExifTag("ISOSpeedRatings", 34855),
251            new ExifTag("OECF", 34856),
252            new ExifTag("ExifVersion", 36864),
253            new ExifTag("DateTimeOriginal", 36867),
254            new ExifTag("DateTimeDigitized", 36868),
255            new ExifTag("ComponentsConfiguration", 37121),
256            new ExifTag("CompressedBitsPerPixel", 37122),
257            new ExifTag("ShutterSpeedValue", 37377),
258            new ExifTag("ApertureValue", 37378),
259            new ExifTag("BrightnessValue", 37379),
260            new ExifTag("ExposureBiasValue", 37380),
261            new ExifTag("MaxApertureValue", 37381),
262            new ExifTag("SubjectDistance", 37382),
263            new ExifTag("MeteringMode", 37383),
264            new ExifTag("LightSource", 37384),
265            new ExifTag("Flash", 37385),
266            new ExifTag("FocalLength", 37386),
267            new ExifTag("SubjectArea", 37396),
268            new ExifTag("MakerNote", 37500),
269            new ExifTag("UserComment", 37510),
270            new ExifTag("SubSecTime", 37520),
271            new ExifTag("SubSecTimeOriginal", 37521),
272            new ExifTag("SubSecTimeDigitized", 37522),
273            new ExifTag("FlashpixVersion", 40960),
274            new ExifTag("ColorSpace", 40961),
275            new ExifTag("PixelXDimension", 40962),
276            new ExifTag("PixelYDimension", 40963),
277            new ExifTag("RelatedSoundFile", 40964),
278            new ExifTag("InteroperabilityIFDPointer", 40965),
279            new ExifTag("FlashEnergy", 41483),
280            new ExifTag("SpatialFrequencyResponse", 41484),
281            new ExifTag("FocalPlaneXResolution", 41486),
282            new ExifTag("FocalPlaneYResolution", 41487),
283            new ExifTag("FocalPlaneResolutionUnit", 41488),
284            new ExifTag("SubjectLocation", 41492),
285            new ExifTag("ExposureIndex", 41493),
286            new ExifTag("SensingMethod", 41495),
287            new ExifTag("FileSource", 41728),
288            new ExifTag("SceneType", 41729),
289            new ExifTag("CFAPattern", 41730),
290            new ExifTag("CustomRendered", 41985),
291            new ExifTag("ExposureMode", 41986),
292            new ExifTag("WhiteBalance", 41987),
293            new ExifTag("DigitalZoomRatio", 41988),
294            new ExifTag("FocalLengthIn35mmFilm", 41989),
295            new ExifTag("SceneCaptureType", 41990),
296            new ExifTag("GainControl", 41991),
297            new ExifTag("Contrast", 41992),
298            new ExifTag("Saturation", 41993),
299            new ExifTag("Sharpness", 41994),
300            new ExifTag("DeviceSettingDescription", 41995),
301            new ExifTag("SubjectDistanceRange", 41996),
302            new ExifTag("ImageUniqueID", 42016),
303    };
304    // Primary image IFD GPS Info tags (See JEITA CP-3451 Table 16. page 56).
305    private static final ExifTag[] IFD_GPS_TAGS = new ExifTag[] {
306            new ExifTag("GPSVersionID", 0),
307            new ExifTag("GPSLatitudeRef", 1),
308            new ExifTag("GPSLatitude", 2),
309            new ExifTag("GPSLongitudeRef", 3),
310            new ExifTag("GPSLongitude", 4),
311            new ExifTag("GPSAltitudeRef", 5),
312            new ExifTag("GPSAltitude", 6),
313            new ExifTag("GPSTimeStamp", 7),
314            new ExifTag("GPSSatellites", 8),
315            new ExifTag("GPSStatus", 9),
316            new ExifTag("GPSMeasureMode", 10),
317            new ExifTag("GPSDOP", 11),
318            new ExifTag("GPSSpeedRef", 12),
319            new ExifTag("GPSSpeed", 13),
320            new ExifTag("GPSTrackRef", 14),
321            new ExifTag("GPSTrack", 15),
322            new ExifTag("GPSImgDirectionRef", 16),
323            new ExifTag("GPSImgDirection", 17),
324            new ExifTag("GPSMapDatum", 18),
325            new ExifTag("GPSDestLatitudeRef", 19),
326            new ExifTag("GPSDestLatitude", 20),
327            new ExifTag("GPSDestLongitudeRef", 21),
328            new ExifTag("GPSDestLongitude", 22),
329            new ExifTag("GPSDestBearingRef", 23),
330            new ExifTag("GPSDestBearing", 24),
331            new ExifTag("GPSDestDistanceRef", 25),
332            new ExifTag("GPSDestDistance", 26),
333            new ExifTag("GPSProcessingMethod", 27),
334            new ExifTag("GPSAreaInformation", 28),
335            new ExifTag("GPSDateStamp", 29),
336            new ExifTag("GPSDifferential", 30),
337    };
338    // Primary image IFD Interoperability tag (See JEITA CP-3451 Table 17. page 56).
339    private static final ExifTag[] IFD_INTEROPERABILITY_TAGS = new ExifTag[] {
340            new ExifTag("InteroperabilityIndex", 1),
341    };
342    // IFD Thumbnail tags (See JEITA CP-3451 Table 18. page 57).
343    private static final ExifTag[] IFD_THUMBNAIL_TAGS = new ExifTag[] {
344            new ExifTag("ThumbnailImageWidth", 256),
345            new ExifTag("ThumbnailImageLength", 257),
346            new ExifTag("BitsPerSample", 258),
347            new ExifTag("Compression", 259),
348            new ExifTag("PhotometricInterpretation", 262),
349            new ExifTag("ImageDescription", 270),
350            new ExifTag("Make", 271),
351            new ExifTag("Model", 272),
352            new ExifTag("StripOffsets", 273),
353            new ExifTag("Orientation", 274),
354            new ExifTag("SamplesPerPixel", 277),
355            new ExifTag("RowsPerStrip", 278),
356            new ExifTag("StripByteCounts", 279),
357            new ExifTag("XResolution", 282),
358            new ExifTag("YResolution", 283),
359            new ExifTag("PlanarConfiguration", 284),
360            new ExifTag("ResolutionUnit", 296),
361            new ExifTag("TransferFunction", 301),
362            new ExifTag("Software", 305),
363            new ExifTag("DateTime", 306),
364            new ExifTag("Artist", 315),
365            new ExifTag("WhitePoint", 318),
366            new ExifTag("PrimaryChromaticities", 319),
367            new ExifTag("JPEGInterchangeFormat", 513),
368            new ExifTag("JPEGInterchangeFormatLength", 514),
369            new ExifTag("YCbCrCoefficients", 529),
370            new ExifTag("YCbCrSubSampling", 530),
371            new ExifTag("YCbCrPositioning", 531),
372            new ExifTag("ReferenceBlackWhite", 532),
373            new ExifTag("Copyright", 33432),
374            new ExifTag("ExifIFDPointer", 34665),
375            new ExifTag("GPSInfoIFDPointer", 34853),
376    };
377
378    // See JEITA CP-3451 Figure 5. page 9.
379    // The following values are used for indicating pointers to the other Image File Directorys.
380
381    // Indices of Exif Ifd tag groups
382    private static final int IFD_TIFF_HINT = 0;
383    private static final int IFD_EXIF_HINT = 1;
384    private static final int IFD_GPS_HINT = 2;
385    private static final int IFD_INTEROPERABILITY_HINT = 3;
386    private static final int IFD_THUMBNAIL_HINT = 4;
387    // List of Exif tag groups
388    private static final ExifTag[][] EXIF_TAGS = new ExifTag[][] {
389            IFD_TIFF_TAGS, IFD_EXIF_TAGS, IFD_GPS_TAGS, IFD_INTEROPERABILITY_TAGS,
390            IFD_THUMBNAIL_TAGS
391    };
392    // List of tags for pointing to the other image file directory offset.
393    private static final ExifTag[] IFD_POINTER_TAGS = new ExifTag[] {
394            new ExifTag("ExifIFDPointer", 34665),
395            new ExifTag("GPSInfoPointer", 34853),
396            new ExifTag("InteroperabilityIFDPointer", 40965),
397    };
398    // List of indices of the indicated tag groups according to the IFD_POINTER_TAGS
399    private static final int[] IFD_POINTER_TAG_HINTS = new int[] {
400            IFD_EXIF_HINT, IFD_GPS_HINT, IFD_INTEROPERABILITY_HINT
401    };
402    // Tags for indicating the thumbnail offset and length
403    private static final ExifTag JPEG_INTERCHANGE_FORMAT_TAG =
404            new ExifTag("JPEGInterchangeFormat", 513);
405    private static final ExifTag JPEG_INTERCHANGE_FORMAT_LENGTH_TAG =
406            new ExifTag("JPEGInterchangeFormatLength", 514);
407
408    // Mappings from tag number to tag name and each item represents one IFD tag group.
409    private static final HashMap[] sExifTagMapsForReading = new HashMap[EXIF_TAGS.length];
410    // Mapping from tag name to tag number and the corresponding tag group.
411    private static final HashMap<String, Pair<Integer, Integer>> sExifTagMapForWriting
412            = new HashMap<>();
413
414    // See JPEG File Interchange Format Version 1.02.
415    // The following values are defined for handling JPEG streams. In this implementation, we are
416    // not only getting information from EXIF but also from some JPEG special segments such as
417    // MARKER_COM for user comment and MARKER_SOFx for image width and height.
418
419    // Identifier for APP1 segment in JPEG
420    private static final byte[] IDENTIFIER_APP1 = "Exif\0\0".getBytes(Charset.forName("US-ASCII"));
421    // JPEG segment markers, that each marker consumes two bytes beginning with 0xff and ending with
422    // the indicator. There is no SOF4, SOF8, SOF16 markers in JPEG and SOFx markers indicates start
423    // of frame(baseline DCT) and the image size info exists in its beginning part.
424    private static final byte MARKER = (byte) 0xff;
425    private static final byte MARKER_SOI = (byte) 0xd8;
426    private static final byte MARKER_SOF0 = (byte) 0xc0;
427    private static final byte MARKER_SOF1 = (byte) 0xc1;
428    private static final byte MARKER_SOF2 = (byte) 0xc2;
429    private static final byte MARKER_SOF3 = (byte) 0xc3;
430    private static final byte MARKER_SOF5 = (byte) 0xc5;
431    private static final byte MARKER_SOF6 = (byte) 0xc6;
432    private static final byte MARKER_SOF7 = (byte) 0xc7;
433    private static final byte MARKER_SOF9 = (byte) 0xc9;
434    private static final byte MARKER_SOF10 = (byte) 0xca;
435    private static final byte MARKER_SOF11 = (byte) 0xcb;
436    private static final byte MARKER_SOF13 = (byte) 0xcd;
437    private static final byte MARKER_SOF14 = (byte) 0xce;
438    private static final byte MARKER_SOF15 = (byte) 0xcf;
439    private static final byte MARKER_SOS = (byte) 0xda;
440    private static final byte MARKER_APP1 = (byte) 0xe1;
441    private static final byte MARKER_COM = (byte) 0xfe;
442    private static final byte MARKER_EOI = (byte) 0xd9;
443
444    static {
445        System.loadLibrary("media_jni");
446        initRawNative();
447        sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
448        sFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
449
450        // Build up the hash tables to look up Exif tags for reading Exif tags.
451        for (int hint = 0; hint < EXIF_TAGS.length; ++hint) {
452            sExifTagMapsForReading[hint] = new HashMap();
453            for (ExifTag tag : EXIF_TAGS[hint]) {
454                sExifTagMapsForReading[hint].put(tag.number, tag.name);
455            }
456        }
457
458        // Build up the hash tables to look up Exif tags for writing Exif tags.
459        // There are some tags that have the same tag name in the different group. For that tags,
460        // Primary image TIFF IFD and Exif private IFD have a higher priority to map than the other
461        // tag groups. For the same tags, it writes one tag in the only one IFD group, which has the
462        // higher priority group.
463        for (int hint = EXIF_TAGS.length - 1; hint >= 0; --hint) {
464            for (ExifTag tag : EXIF_TAGS[hint]) {
465                sExifTagMapForWriting.put(tag.name, new Pair<>(tag.number, hint));
466            }
467        }
468    }
469
470    private final String mFilename;
471    private final FileDescriptor mFileDescriptor;
472    private final InputStream mInputStream;
473    private boolean mIsRaw;
474    private final HashMap<String, String> mAttributes = new HashMap<>();
475    private boolean mHasThumbnail;
476    // The following values used for indicating a thumbnail position.
477    private int mThumbnailOffset;
478    private int mThumbnailLength;
479    private byte[] mThumbnailBytes;
480
481    // Pattern to check non zero timestamp
482    private static final Pattern sNonZeroTimePattern = Pattern.compile(".*[1-9].*");
483
484    /**
485     * Reads Exif tags from the specified image file.
486     */
487    public ExifInterface(String filename) throws IOException {
488        if (filename == null) {
489            throw new IllegalArgumentException("filename cannot be null");
490        }
491        mFilename = filename;
492        mFileDescriptor = null;
493        mInputStream = new FileInputStream(filename);
494        loadAttributes();
495    }
496
497    /**
498     * Reads Exif tags from the specified image file descriptor.
499     */
500    public ExifInterface(FileDescriptor fileDescriptor) throws IOException {
501        if (fileDescriptor == null) {
502            throw new IllegalArgumentException("parcelFileDescriptor cannot be null");
503        }
504        mFilename = null;
505        mFileDescriptor = fileDescriptor;
506        mInputStream = new FileInputStream(fileDescriptor);
507        loadAttributes();
508    }
509
510    /**
511     * Reads Exif tags from the specified image input stream. Attribute mutation is not supported
512     * for input streams.
513     */
514    public ExifInterface(InputStream inputStream) throws IOException {
515        if (inputStream == null) {
516            throw new IllegalArgumentException("inputStream cannot be null");
517        }
518        mFilename = null;
519        mFileDescriptor = null;
520        mInputStream = inputStream;
521        loadAttributes();
522    }
523
524    /**
525     * Returns the value of the specified tag or {@code null} if there
526     * is no such tag in the image file.
527     *
528     * @param tag the name of the tag.
529     */
530    public String getAttribute(String tag) {
531        return mAttributes.get(tag);
532    }
533
534    /**
535     * Returns the integer value of the specified tag. If there is no such tag
536     * in the image file or the value cannot be parsed as integer, return
537     * <var>defaultValue</var>.
538     *
539     * @param tag the name of the tag.
540     * @param defaultValue the value to return if the tag is not available.
541     */
542    public int getAttributeInt(String tag, int defaultValue) {
543        String value = mAttributes.get(tag);
544        if (value == null) return defaultValue;
545        try {
546            return Integer.valueOf(value);
547        } catch (NumberFormatException ex) {
548            return defaultValue;
549        }
550    }
551
552    /**
553     * Returns the double value of the tag that is specified as rational or contains a
554     * double-formatted value. If there is no such tag in the image file or the value cannot be
555     * parsed as double, return <var>defaultValue</var>.
556     *
557     * @param tag the name of the tag.
558     * @param defaultValue the value to return if the tag is not available.
559     */
560    public double getAttributeDouble(String tag, double defaultValue) {
561        String value = mAttributes.get(tag);
562        if (value == null) return defaultValue;
563        try {
564            int index = value.indexOf("/");
565            if (index == -1) return Double.parseDouble(value);
566            double denom = Double.parseDouble(value.substring(index + 1));
567            if (denom == 0) return defaultValue;
568            double num = Double.parseDouble(value.substring(0, index));
569            return num / denom;
570        } catch (NumberFormatException ex) {
571            return defaultValue;
572        }
573    }
574
575    /**
576     * Set the value of the specified tag.
577     *
578     * @param tag the name of the tag.
579     * @param value the value of the tag.
580     */
581    public void setAttribute(String tag, String value) {
582        if (value == null) {
583            mAttributes.remove(tag);
584            return;
585        }
586        mAttributes.put(tag, value);
587    }
588
589    /**
590     * Initialize mAttributes with the attributes from the file mFilename.
591     *
592     * mAttributes is a HashMap which stores the Exif attributes of the file.
593     * The key is the standard tag name and the value is the tag's value: e.g.
594     * Model -&gt; Nikon. Numeric values are stored as strings.
595     *
596     * This function also initialize mHasThumbnail to indicate whether the
597     * file has a thumbnail inside.
598     */
599    private void loadAttributes() throws IOException {
600        FileInputStream in = null;
601        try {
602            if (mFilename != null) {
603                in = new FileInputStream(mFilename);
604            }
605            if (mFileDescriptor != null) {
606                in = new FileInputStream(mFileDescriptor);
607            }
608            if (in != null) {
609                // First test whether a given file is a one of RAW format or not.
610                HashMap map = getRawAttributesNative(Os.dup(in.getFD()));
611                mIsRaw = map != null;
612                if (mIsRaw) {
613                    for (Object obj : map.entrySet()) {
614                        Map.Entry entry = (Map.Entry) obj;
615                        String attrName = (String) entry.getKey();
616                        String attrValue = (String) entry.getValue();
617
618                        switch (attrName) {
619                            case TAG_HAS_THUMBNAIL:
620                                mHasThumbnail = attrValue.equalsIgnoreCase("true");
621                                break;
622                            case TAG_THUMBNAIL_OFFSET:
623                                mThumbnailOffset = Integer.parseInt(attrValue);
624                                break;
625                            case TAG_THUMBNAIL_LENGTH:
626                                mThumbnailLength = Integer.parseInt(attrValue);
627                                break;
628                            default:
629                                mAttributes.put(attrName, attrValue);
630                                break;
631                        }
632                    }
633
634                    if (DEBUG) {
635                        printAttributes();
636                    }
637                    return;
638                }
639            }
640        } catch (ErrnoException e) {
641            e.rethrowAsIOException();
642        } finally {
643            IoUtils.closeQuietly(in);
644        }
645
646        try {
647            if (mFileDescriptor != null) {
648                Os.lseek(mFileDescriptor, 0, OsConstants.SEEK_SET);
649            }
650
651            getJpegAttributes(mInputStream);
652        } catch (ErrnoException e) {
653            e.rethrowAsIOException();
654        } finally {
655            IoUtils.closeQuietly(mInputStream);
656        }
657
658        if (DEBUG) {
659            printAttributes();
660        }
661    }
662
663    // Prints out attributes for debugging.
664    private void printAttributes() {
665        Log.d(TAG, "The size of tags: " + mAttributes.size());
666        for (Map.Entry<String, String> entry : mAttributes.entrySet()) {
667            Log.d(TAG, "tagName: " + entry.getKey() + ", tagValue: " + entry.getValue());
668        }
669    }
670
671    /**
672     * Save the tag data into the original image file. This is expensive because it involves
673     * copying all the data from one file to another and deleting the old file and renaming the
674     * other. It's best to use{@link #setAttribute(String,String)} to set all attributes to write
675     * and make a single call rather than multiple calls for each attribute.
676     */
677    public void saveAttributes() throws IOException {
678        if (mIsRaw) {
679            throw new UnsupportedOperationException(
680                    "ExifInterface does not support saving attributes on RAW formats.");
681        }
682        if (mFileDescriptor == null && mFilename == null) {
683            throw new UnsupportedOperationException(
684                    "ExifInterface does not support saving attributes for input streams.");
685        }
686
687        // Keep the thumbnail in memory
688        mThumbnailBytes = getThumbnail();
689
690        FileInputStream in = null;
691        FileOutputStream out = null;
692        File tempFile = null;
693        try {
694            // Move the original file to temporary file.
695            if (mFilename != null) {
696                tempFile = new File(mFilename + ".tmp");
697                File originalFile = new File(mFilename);
698                if (!originalFile.renameTo(tempFile)) {
699                    throw new IOException("Could'nt rename to " + tempFile.getAbsolutePath());
700                }
701            }
702            if (mFileDescriptor != null) {
703                tempFile = File.createTempFile("temp", "jpg");
704                Os.lseek(mFileDescriptor, 0, OsConstants.SEEK_SET);
705                in = new FileInputStream(mFileDescriptor);
706                out = new FileOutputStream(tempFile);
707                Streams.copy(in, out);
708            }
709        } catch (ErrnoException e) {
710            e.rethrowAsIOException();
711        } finally {
712            IoUtils.closeQuietly(in);
713            IoUtils.closeQuietly(out);
714        }
715
716        in = null;
717        out = null;
718        try {
719            // Save the new file.
720            in = new FileInputStream(tempFile);
721            if (mFilename != null) {
722                out = new FileOutputStream(mFilename);
723            }
724            if (mFileDescriptor != null) {
725                Os.lseek(mFileDescriptor, 0, OsConstants.SEEK_SET);
726                out = new FileOutputStream(mFileDescriptor);
727            }
728            saveJpegAttributes(in, out);
729        } catch (ErrnoException e) {
730            e.rethrowAsIOException();
731        } finally {
732            IoUtils.closeQuietly(in);
733            IoUtils.closeQuietly(out);
734            tempFile.delete();
735        }
736
737        // Discard the thumbnail in memory
738        mThumbnailBytes = null;
739    }
740
741    /**
742     * Returns true if the image file has a thumbnail.
743     */
744    public boolean hasThumbnail() {
745        return mHasThumbnail;
746    }
747
748    /**
749     * Returns the thumbnail inside the image file, or {@code null} if there is no thumbnail.
750     * The returned data is in JPEG format and can be decoded using
751     * {@link android.graphics.BitmapFactory#decodeByteArray(byte[],int,int)}
752     */
753    public byte[] getThumbnail() {
754        if (!mHasThumbnail) {
755            return null;
756        }
757        if (mThumbnailBytes != null) {
758            return mThumbnailBytes;
759        }
760
761        // Read the thumbnail.
762        FileInputStream in = null;
763        try  {
764            if (mFileDescriptor != null) {
765                Os.lseek(mFileDescriptor, 0, OsConstants.SEEK_SET);
766                in = new FileInputStream(mFileDescriptor);
767            }
768            if (mFilename != null) {
769                in = new FileInputStream(mFilename);
770            }
771            if (in == null) {
772                // Should not be reached this.
773                throw new FileNotFoundException();
774            }
775            if (in.skip(mThumbnailOffset) != mThumbnailOffset) {
776                throw new IOException("Corrupted image");
777            }
778            byte[] buffer = new byte[mThumbnailLength];
779            if (in.read(buffer) != mThumbnailLength) {
780                throw new IOException("Corrupted image");
781            }
782            return buffer;
783        } catch (IOException | ErrnoException e) {
784            // Couldn't get a thumbnail image.
785        } finally {
786            IoUtils.closeQuietly(in);
787        }
788        return null;
789    }
790
791    /**
792     * Returns the offset and length of thumbnail inside the image file, or
793     * {@code null} if there is no thumbnail.
794     *
795     * @return two-element array, the offset in the first value, and length in
796     *         the second, or {@code null} if no thumbnail was found.
797     * @hide
798     */
799    public long[] getThumbnailRange() {
800        long[] range = new long[2];
801        range[0] = mThumbnailOffset;
802        range[1] = mThumbnailLength;
803        return range;
804    }
805
806    /**
807     * Stores the latitude and longitude value in a float array. The first element is
808     * the latitude, and the second element is the longitude. Returns false if the
809     * Exif tags are not available.
810     */
811    public boolean getLatLong(float output[]) {
812        String latValue = mAttributes.get(TAG_GPS_LATITUDE);
813        String latRef = mAttributes.get(TAG_GPS_LATITUDE_REF);
814        String lngValue = mAttributes.get(TAG_GPS_LONGITUDE);
815        String lngRef = mAttributes.get(TAG_GPS_LONGITUDE_REF);
816
817        if (latValue != null && latRef != null && lngValue != null && lngRef != null) {
818            try {
819                output[0] = convertRationalLatLonToFloat(latValue, latRef);
820                output[1] = convertRationalLatLonToFloat(lngValue, lngRef);
821                return true;
822            } catch (IllegalArgumentException e) {
823                // if values are not parseable
824            }
825        }
826
827        return false;
828    }
829
830    /**
831     * Return the altitude in meters. If the exif tag does not exist, return
832     * <var>defaultValue</var>.
833     *
834     * @param defaultValue the value to return if the tag is not available.
835     */
836    public double getAltitude(double defaultValue) {
837        double altitude = getAttributeDouble(TAG_GPS_ALTITUDE, -1);
838        int ref = getAttributeInt(TAG_GPS_ALTITUDE_REF, -1);
839
840        if (altitude >= 0 && ref >= 0) {
841            return (altitude * ((ref == 1) ? -1 : 1));
842        } else {
843            return defaultValue;
844        }
845    }
846
847    /**
848     * Returns number of milliseconds since Jan. 1, 1970, midnight local time.
849     * Returns -1 if the date time information if not available.
850     * @hide
851     */
852    public long getDateTime() {
853        String dateTimeString = mAttributes.get(TAG_DATETIME);
854        if (dateTimeString == null
855                || !sNonZeroTimePattern.matcher(dateTimeString).matches()) return -1;
856
857        ParsePosition pos = new ParsePosition(0);
858        try {
859            // The exif field is in local time. Parsing it as if it is UTC will yield time
860            // since 1/1/1970 local time
861            Date datetime = sFormatter.parse(dateTimeString, pos);
862            if (datetime == null) return -1;
863            long msecs = datetime.getTime();
864
865            String subSecs = mAttributes.get(TAG_SUBSECTIME);
866            if (subSecs != null) {
867                try {
868                    long sub = Long.valueOf(subSecs);
869                    while (sub > 1000) {
870                        sub /= 10;
871                    }
872                    msecs += sub;
873                } catch (NumberFormatException e) {
874                    // Ignored
875                }
876            }
877            return msecs;
878        } catch (IllegalArgumentException ex) {
879            return -1;
880        }
881    }
882
883    /**
884     * Returns number of milliseconds since Jan. 1, 1970, midnight UTC.
885     * Returns -1 if the date time information if not available.
886     * @hide
887     */
888    public long getGpsDateTime() {
889        String date = mAttributes.get(TAG_GPS_DATESTAMP);
890        String time = mAttributes.get(TAG_GPS_TIMESTAMP);
891        if (date == null || time == null
892                || (!sNonZeroTimePattern.matcher(date).matches()
893                && !sNonZeroTimePattern.matcher(time).matches())) return -1;
894
895        String dateTimeString = date + ' ' + time;
896
897        ParsePosition pos = new ParsePosition(0);
898        try {
899            Date datetime = sFormatter.parse(dateTimeString, pos);
900            if (datetime == null) return -1;
901            return datetime.getTime();
902        } catch (IllegalArgumentException ex) {
903            return -1;
904        }
905    }
906
907    private static float convertRationalLatLonToFloat(String rationalString, String ref) {
908        try {
909            String [] parts = rationalString.split(",");
910
911            String [] pair;
912            pair = parts[0].split("/");
913            double degrees = Double.parseDouble(pair[0].trim())
914                    / Double.parseDouble(pair[1].trim());
915
916            pair = parts[1].split("/");
917            double minutes = Double.parseDouble(pair[0].trim())
918                    / Double.parseDouble(pair[1].trim());
919
920            pair = parts[2].split("/");
921            double seconds = Double.parseDouble(pair[0].trim())
922                    / Double.parseDouble(pair[1].trim());
923
924            double result = degrees + (minutes / 60.0) + (seconds / 3600.0);
925            if ((ref.equals("S") || ref.equals("W"))) {
926                return (float) -result;
927            }
928            return (float) result;
929        } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
930            // Not valid
931            throw new IllegalArgumentException();
932        }
933    }
934
935    // Loads EXIF attributes from a JPEG input stream.
936    private void getJpegAttributes(InputStream inputStream) throws IOException {
937        // See JPEG File Interchange Format Specification page 5.
938        if (DEBUG) {
939            Log.d(TAG, "getJpegAttributes starting with: " + inputStream);
940        }
941        DataInputStream dataInputStream = new DataInputStream(inputStream);
942        byte marker;
943        int bytesRead = 0;
944        ++bytesRead;
945        if ((marker = dataInputStream.readByte()) != MARKER) {
946            throw new IOException("Invalid marker: " + Integer.toHexString(marker & 0xff));
947        }
948        ++bytesRead;
949        if (dataInputStream.readByte() != MARKER_SOI) {
950            throw new IOException("Invalid marker: " + Integer.toHexString(marker & 0xff));
951        }
952        while (true) {
953            ++bytesRead;
954            marker = dataInputStream.readByte();
955            if (marker != MARKER) {
956                throw new IOException("Invalid marker:" + Integer.toHexString(marker & 0xff));
957            }
958            ++bytesRead;
959            marker = dataInputStream.readByte();
960            if (DEBUG) {
961                Log.d(TAG, "Found JPEG segment indicator: " + Integer.toHexString(marker & 0xff));
962            }
963
964            // EOI indicates the end of an image and in case of SOS, JPEG image stream starts and
965            // the image data will terminate right after.
966            if (marker == MARKER_EOI || marker == MARKER_SOS) {
967                break;
968            }
969            bytesRead += 2;
970            int length = dataInputStream.readUnsignedShort() - 2;
971            if (length < 0)
972                throw new IOException("Invalid length");
973            bytesRead += length;
974            switch (marker) {
975                case MARKER_APP1: {
976                    if (DEBUG) {
977                        Log.d(TAG, "MARKER_APP1");
978                    }
979                    bytesRead -= length;
980                    if (length < 6) {
981                        throw new IOException("Invalid exif");
982                    }
983                    byte[] identifier = new byte[6];
984                    if (inputStream.read(identifier) != 6) {
985                        throw new IOException("Invalid exif");
986                    }
987                    if (!Arrays.equals(identifier, IDENTIFIER_APP1)) {
988                        throw new IOException("Invalid app1 identifier");
989                    }
990                    bytesRead += 6;
991                    length -= 6;
992                    if (length <= 0) {
993                        throw new IOException("Invalid exif");
994                    }
995                    byte[] bytes = new byte[length];
996                    if (dataInputStream.read(bytes) != length) {
997                        throw new IOException("Invalid exif");
998                    }
999                    readExifSegment(bytes, bytesRead);
1000                    bytesRead += length;
1001                    length = 0;
1002                    break;
1003                }
1004
1005                case MARKER_COM: {
1006                    byte[] bytes = new byte[length];
1007                    if (dataInputStream.read(bytes) != length) {
1008                        throw new IOException("Invalid exif");
1009                    }
1010                    mAttributes.put("UserComment",
1011                            new String(bytes, Charset.forName("US-ASCII")));
1012                    break;
1013                }
1014
1015                case MARKER_SOF0:
1016                case MARKER_SOF1:
1017                case MARKER_SOF2:
1018                case MARKER_SOF3:
1019                case MARKER_SOF5:
1020                case MARKER_SOF6:
1021                case MARKER_SOF7:
1022                case MARKER_SOF9:
1023                case MARKER_SOF10:
1024                case MARKER_SOF11:
1025                case MARKER_SOF13:
1026                case MARKER_SOF14:
1027                case MARKER_SOF15: {
1028                    dataInputStream.skipBytes(1);
1029                    mAttributes.put("ImageLength",
1030                            String.valueOf(dataInputStream.readUnsignedShort()));
1031                    mAttributes.put("ImageWidth",
1032                            String.valueOf(dataInputStream.readUnsignedShort()));
1033                    length -= 5;
1034                    break;
1035                }
1036
1037                default: {
1038                    break;
1039                }
1040            }
1041            if (length < 0) {
1042                throw new IOException("Invalid length");
1043            }
1044            dataInputStream.skipBytes(length);
1045        }
1046    }
1047
1048    // Stores a new JPEG image with EXIF attributes into a given output stream.
1049    private void saveJpegAttributes(InputStream inputStream, OutputStream outputStream)
1050            throws IOException {
1051        // See JPEG File Interchange Format Specification page 5.
1052        if (DEBUG) {
1053            Log.d(TAG, "saveJpegAttributes starting with (inputStream: " + inputStream
1054                    + ", outputStream: " + outputStream + ")");
1055        }
1056        DataInputStream dataInputStream = new DataInputStream(inputStream);
1057        ExifDataOutputStream dataOutputStream = new ExifDataOutputStream(outputStream);
1058        int bytesRead = 0;
1059        ++bytesRead;
1060        if (dataInputStream.readByte() != MARKER) {
1061            throw new IOException("Invalid marker");
1062        }
1063        dataOutputStream.writeByte(MARKER);
1064        ++bytesRead;
1065        if (dataInputStream.readByte() != MARKER_SOI) {
1066            throw new IOException("Invalid marker");
1067        }
1068        dataOutputStream.writeByte(MARKER_SOI);
1069
1070        byte[] bytes = new byte[4096];
1071
1072        while (true) {
1073            ++bytesRead;
1074            if (dataInputStream.readByte() != MARKER) {
1075                throw new IOException("Invalid marker");
1076            }
1077            dataOutputStream.writeByte(MARKER);
1078            ++bytesRead;
1079            byte marker = dataInputStream.readByte();
1080            dataOutputStream.writeByte(marker);
1081            switch (marker) {
1082                case MARKER_APP1: {
1083                    // Rewrite EXIF segment
1084                    int length = dataInputStream.readUnsignedShort() - 2;
1085                    if (length < 0)
1086                        throw new IOException("Invalid length");
1087                    bytesRead += 2;
1088                    int read;
1089                    while ((read = dataInputStream.read(
1090                            bytes, 0, Math.min(length, bytes.length))) > 0) {
1091                        length -= read;
1092                    }
1093                    bytesRead += length;
1094                    writeExifSegment(dataOutputStream, bytesRead);
1095                    break;
1096                }
1097                case MARKER_EOI:
1098                case MARKER_SOS: {
1099                    // Copy all the remaining data
1100                    Streams.copy(dataInputStream, dataOutputStream);
1101                    return;
1102                }
1103                default: {
1104                    // Copy JPEG segment
1105                    int length = dataInputStream.readUnsignedShort();
1106                    dataOutputStream.writeUnsignedShort(length);
1107                    if (length < 0)
1108                        throw new IOException("Invalid length");
1109                    length -= 2;
1110                    bytesRead += 2;
1111                    int read;
1112                    while ((read = dataInputStream.read(
1113                            bytes, 0, Math.min(length, bytes.length))) > 0) {
1114                        dataOutputStream.write(bytes, 0, read);
1115                        length -= read;
1116                    }
1117                    bytesRead += length;
1118                    break;
1119                }
1120            }
1121        }
1122    }
1123
1124    // Reads the given EXIF byte area and save its tag data into attributes.
1125    private void readExifSegment(byte[] exifBytes, int exifOffsetFromBeginning) throws IOException {
1126        // Parse TIFF Headers. See JEITA CP-3451C Table 1. page 10.
1127        ByteOrderAwarenessDataInputStream dataInputStream =
1128                new ByteOrderAwarenessDataInputStream(exifBytes);
1129
1130        // Read byte align
1131        short byteOrder = dataInputStream.readShort();
1132        switch (byteOrder) {
1133            case BYTE_ALIGN_II:
1134                if (DEBUG) {
1135                    Log.d(TAG, "readExifSegment: Byte Align II");
1136                }
1137                dataInputStream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
1138                break;
1139            case BYTE_ALIGN_MM:
1140                if (DEBUG) {
1141                    Log.d(TAG, "readExifSegment: Byte Align MM");
1142                }
1143                dataInputStream.setByteOrder(ByteOrder.BIG_ENDIAN);
1144                break;
1145            default:
1146                throw new IOException("Invalid byte order: " + Integer.toHexString(byteOrder));
1147        }
1148
1149        int startCode = dataInputStream.readUnsignedShort();
1150        if (startCode != 0x2a) {
1151            throw new IOException("Invalid exif start: " + Integer.toHexString(startCode));
1152        }
1153
1154        // Read first ifd offset
1155        long firstIfdOffset = dataInputStream.readUnsignedInt();
1156        if (firstIfdOffset < 8 || firstIfdOffset >= exifBytes.length) {
1157            throw new IOException("Invalid first Ifd offset: " + firstIfdOffset);
1158        }
1159        firstIfdOffset -= 8;
1160        if (firstIfdOffset > 0) {
1161            if (dataInputStream.skip(firstIfdOffset) != firstIfdOffset)
1162                throw new IOException("Couldn't jump to first Ifd: " + firstIfdOffset);
1163        }
1164
1165        // Read primary image TIFF image file directory.
1166        readImageFileDirectory(dataInputStream, IFD_TIFF_HINT);
1167
1168        // Process thumbnail.
1169        try {
1170            int jpegInterchangeFormat = Integer.parseInt(
1171                    mAttributes.get(JPEG_INTERCHANGE_FORMAT_TAG.name));
1172            int jpegInterchangeFormatLength = Integer.parseInt(
1173                    mAttributes.get(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name));
1174            // The following code limits the size of thumbnail size not to overflow EXIF data area.
1175            jpegInterchangeFormatLength = Math.min(jpegInterchangeFormat
1176                    + jpegInterchangeFormatLength, exifOffsetFromBeginning + exifBytes.length)
1177                    - jpegInterchangeFormat;
1178            if (jpegInterchangeFormat > 0 && jpegInterchangeFormatLength > 0) {
1179                mHasThumbnail = true;
1180                mThumbnailOffset = exifOffsetFromBeginning + jpegInterchangeFormat;
1181                mThumbnailLength = jpegInterchangeFormatLength;
1182
1183                // Do not store a thumbnail in memory if the given input can be re-read.
1184                if (mFileDescriptor == null && mFilename == null) {
1185                    byte[] thumbnailBytes = new byte[jpegInterchangeFormatLength];
1186                    dataInputStream.seek(jpegInterchangeFormat);
1187                    dataInputStream.readFully(thumbnailBytes);
1188                    mThumbnailBytes = thumbnailBytes;
1189
1190                    if (DEBUG) {
1191                        Bitmap bitmap = BitmapFactory.decodeByteArray(
1192                                thumbnailBytes, 0, thumbnailBytes.length);
1193                        Log.d(TAG, "Thumbnail offset: " + mThumbnailOffset + ", length: "
1194                                + mThumbnailLength + ", width: " + bitmap.getWidth() + ", height: "
1195                                + bitmap.getHeight());
1196                    }
1197                }
1198            }
1199        } catch (NumberFormatException e) {
1200            // Ignored the corrupted image.
1201        }
1202
1203        // For compatibility, keep data formats as follows.
1204        convertToInt(TAG_IMAGE_WIDTH);
1205        convertToInt(TAG_IMAGE_LENGTH);
1206        convertToInt(TAG_ORIENTATION);
1207        convertToInt(TAG_FLASH);
1208        convertToRational(TAG_FOCAL_LENGTH);
1209        convertToDouble(TAG_DIGITAL_ZOOM_RATIO);
1210        convertToDouble(TAG_EXPOSURE_TIME);
1211        convertToDouble(TAG_APERTURE);
1212        convertToDouble(TAG_SUBJECT_DISTANCE);
1213        convertToInt(TAG_ISO);
1214        convertToDouble(TAG_EXPOSURE_BIAS_VALUE);
1215        convertToInt(TAG_WHITE_BALANCE);
1216        convertToInt(TAG_LIGHT_SOURCE);
1217        convertToInt(TAG_METERING_MODE);
1218        convertToInt(TAG_EXPOSURE_PROGRAM);
1219        convertToInt(TAG_EXPOSURE_MODE);
1220        convertToRational(TAG_GPS_ALTITUDE);
1221        convertToInt(TAG_GPS_ALTITUDE_REF);
1222        convertToRational(TAG_GPS_LONGITUDE);
1223        convertToRational(TAG_GPS_LATITUDE);
1224        convertToTimetamp(TAG_GPS_TIMESTAMP);
1225
1226        // The value of DATETIME tag has the same value of DATETIME_ORIGINAL tag.
1227        String valueOfDateTimeOriginal = mAttributes.get("DateTimeOriginal");
1228        if (valueOfDateTimeOriginal != null) {
1229            mAttributes.put(TAG_DATETIME, valueOfDateTimeOriginal);
1230        }
1231
1232        // Add the default value.
1233        if (!mAttributes.containsKey(TAG_IMAGE_WIDTH)) {
1234            mAttributes.put(TAG_IMAGE_WIDTH, "0");
1235        }
1236        if (!mAttributes.containsKey(TAG_IMAGE_LENGTH)) {
1237            mAttributes.put(TAG_IMAGE_LENGTH, "0");
1238        }
1239        if (!mAttributes.containsKey(TAG_ORIENTATION)) {
1240            mAttributes.put(TAG_ORIENTATION, "0");
1241        }
1242        if (!mAttributes.containsKey(TAG_LIGHT_SOURCE)) {
1243            mAttributes.put(TAG_LIGHT_SOURCE, "0");
1244        }
1245    }
1246
1247    // Converts the tag value to timestamp; Otherwise deletes the given tag.
1248    private void convertToTimetamp(String tagName) {
1249        String entryValue = mAttributes.get(tagName);
1250        if (entryValue == null) return;
1251        int dataFormat = getDataFormatOfExifEntryValue(entryValue);
1252        String[] components = entryValue.split(",");
1253        if (dataFormat == IFD_FORMAT_SRATIONAL && components.length == 3) {
1254            StringBuilder stringBuilder = new StringBuilder();
1255            for (String component : components) {
1256                if (stringBuilder.length() > 0) {
1257                    stringBuilder.append(":");
1258                }
1259                String[] rationalNumber = component.split("/");
1260                int numerator = Integer.parseInt(rationalNumber[0]);
1261                int denominator = Integer.parseInt(rationalNumber[1]);
1262                if (denominator == 0) {
1263                    numerator = 0;
1264                    denominator = 1;
1265                }
1266                int value = numerator / denominator;
1267                stringBuilder.append(String.format("%02d", value));
1268            }
1269            mAttributes.put(tagName, stringBuilder.toString());
1270        } else if (dataFormat != IFD_FORMAT_STRING) {
1271            mAttributes.remove(tagName);
1272        }
1273    }
1274
1275    // Checks the tag value of a given tag formatted in double type; Otherwise try to convert it to
1276    // double type or delete it.
1277    private void convertToDouble(String tagName) {
1278        String entryValue = mAttributes.get(tagName);
1279        if (entryValue == null) return;
1280        int dataFormat = getDataFormatOfExifEntryValue(entryValue);
1281        switch (dataFormat) {
1282            case IFD_FORMAT_SRATIONAL: {
1283                StringBuilder stringBuilder = new StringBuilder();
1284                String[] components = entryValue.split(",");
1285                for (String component : components) {
1286                    if (stringBuilder.length() > 0) {
1287                        stringBuilder.append(",");
1288                    }
1289                    String[] rationalNumber = component.split("/");
1290                    int numerator = Integer.parseInt(rationalNumber[0]);
1291                    int denominator = Integer.parseInt(rationalNumber[1]);
1292                    if (denominator == 0) {
1293                        numerator = 0;
1294                        denominator = 1;
1295                    }
1296                    stringBuilder.append((double) numerator / denominator);
1297                }
1298                mAttributes.put(tagName, stringBuilder.toString());
1299                break;
1300            }
1301            case IFD_FORMAT_DOUBLE:
1302                // Keep it as is.
1303                break;
1304            default:
1305                mAttributes.remove(tagName);
1306                break;
1307        }
1308    }
1309
1310    // Checks the tag value of a given tag formatted in int type; Otherwise deletes the tag value.
1311    private void convertToRational(String tagName) {
1312        String entryValue = mAttributes.get(tagName);
1313        if (entryValue == null) return;
1314        int dataFormat = getDataFormatOfExifEntryValue(entryValue);
1315        switch (dataFormat) {
1316            case IFD_FORMAT_SLONG:
1317            case IFD_FORMAT_DOUBLE: {
1318                StringBuilder stringBuilder = new StringBuilder();
1319                String[] components = entryValue.split(",");
1320                for (String component : components) {
1321                    if (stringBuilder.length() > 0) {
1322                        stringBuilder.append(",");
1323                    }
1324                    double doubleValue = Double.parseDouble(component);
1325                    stringBuilder.append((int) (doubleValue * 10000.0)).append("/").append(10000);
1326                }
1327                mAttributes.put(tagName, stringBuilder.toString());
1328                break;
1329            }
1330            case IFD_FORMAT_SRATIONAL:
1331                // Keep it as is.
1332                break;
1333            default:
1334                mAttributes.remove(tagName);
1335                break;
1336        }
1337    }
1338
1339    // Checks the tag value of a given tag formatted in int type; Otherwise deletes the tag value.
1340    private void convertToInt(String tagName) {
1341        String entryValue = mAttributes.get(tagName);
1342        if (entryValue == null) return;
1343        int dataFormat = getDataFormatOfExifEntryValue(entryValue);
1344        if (dataFormat != IFD_FORMAT_SLONG) {
1345            mAttributes.remove(tagName);
1346        }
1347    }
1348
1349    // Reads image file directory, which is a tag group in EXIF.
1350    private void readImageFileDirectory(ByteOrderAwarenessDataInputStream dataInputStream, int hint)
1351            throws IOException {
1352        // See JEITA CP-3451 Figure 5. page 9.
1353        short numberOfDirectoryEntry = dataInputStream.readShort();
1354
1355        if (DEBUG) {
1356            Log.d(TAG, "numberOfDirectoryEntry: " + numberOfDirectoryEntry);
1357        }
1358
1359        for (short i = 0; i < numberOfDirectoryEntry; ++i) {
1360            int tagNumber = dataInputStream.readUnsignedShort();
1361            int dataFormat = dataInputStream.readUnsignedShort();
1362            int numberOfComponents = dataInputStream.readInt();
1363            long nextEntryOffset = dataInputStream.peek() + 4;  // next four bytes is for data
1364                                                                // offset or value.
1365
1366            if (DEBUG) {
1367                Log.d(TAG, String.format("tagNumber: %d, dataFormat: %d, numberOfComponents: %d",
1368                        tagNumber, dataFormat, numberOfComponents));
1369            }
1370
1371            // Read a value from data field or seek to the value offset which is stored in data
1372            // field if the size of the entry value is bigger than 4.
1373            int byteCount = numberOfComponents * IFD_FORMAT_BYTES_PER_FORMAT[dataFormat];
1374            if (byteCount > 4) {
1375                long offset = dataInputStream.readUnsignedInt();
1376                if (DEBUG) {
1377                    Log.d(TAG, "seek to data offset: " + offset);
1378                }
1379                dataInputStream.seek(offset);
1380            }
1381
1382            // Look up a corresponding tag from tag number
1383            String tagName = (String) sExifTagMapsForReading[hint].get(tagNumber);
1384            // Skip if the parsed tag number is not defined.
1385            if (tagName == null) {
1386                dataInputStream.seek(nextEntryOffset);
1387                continue;
1388            }
1389
1390            // Recursively parse IFD when a IFD pointer tag appears.
1391            int innerIfdHint = getIfdHintFromTagNumber(tagNumber);
1392            if (innerIfdHint >= 0) {
1393                long offset = -1L;
1394                // Get offset from data field
1395                switch (dataFormat) {
1396                    case IFD_FORMAT_USHORT: {
1397                        offset = dataInputStream.readUnsignedShort();
1398                        break;
1399                    }
1400                    case IFD_FORMAT_SSHORT: {
1401                        offset = dataInputStream.readShort();
1402                        break;
1403                    }
1404                    case IFD_FORMAT_ULONG: {
1405                        offset = dataInputStream.readUnsignedInt();
1406                        break;
1407                    }
1408                    case IFD_FORMAT_SLONG: {
1409                        offset = dataInputStream.readInt();
1410                        break;
1411                    }
1412                    default: {
1413                        // Nothing to do
1414                        break;
1415                    }
1416                }
1417                if (DEBUG) {
1418                    Log.d(TAG, String.format("Offset: %d, tagName: %s", offset, tagName));
1419                }
1420                if (offset > 0L) {
1421                    dataInputStream.seek(offset);
1422                    readImageFileDirectory(dataInputStream, innerIfdHint);
1423                }
1424
1425                dataInputStream.seek(nextEntryOffset);
1426                continue;
1427            }
1428
1429            if (numberOfComponents == 1 || dataFormat == IFD_FORMAT_STRING
1430                    || dataFormat == IFD_FORMAT_UNDEFINED) {
1431                String entryValue = readExifEntryValue(
1432                        dataInputStream, dataFormat, numberOfComponents);
1433                if (entryValue != null) {
1434                    mAttributes.put(tagName, entryValue);
1435                }
1436            } else {
1437                StringBuilder entryValueBuilder = new StringBuilder();
1438                for (int c = 0; c < numberOfComponents; ++c) {
1439                    if (entryValueBuilder.length() > 0) {
1440                        entryValueBuilder.append(",");
1441                    }
1442                    entryValueBuilder.append(readExifEntryValue(
1443                            dataInputStream, dataFormat, numberOfComponents));
1444                }
1445                mAttributes.put(tagName, entryValueBuilder.toString());
1446            }
1447
1448            if (dataInputStream.peek() != nextEntryOffset) {
1449                dataInputStream.seek(nextEntryOffset);
1450            }
1451        }
1452
1453        long nextIfdOffset = dataInputStream.readUnsignedInt();
1454        if (DEBUG) {
1455            Log.d(TAG, String.format("nextIfdOffset: %d", nextIfdOffset));
1456        }
1457        // The next IFD offset needs to be bigger than 8 since the first IFD offset is at least 8.
1458        if (nextIfdOffset > 8) {
1459            dataInputStream.seek(nextIfdOffset);
1460            readImageFileDirectory(dataInputStream, IFD_THUMBNAIL_HINT);
1461        }
1462    }
1463
1464    // Reads a value from where the entry value are stored.
1465    private String readExifEntryValue(ByteOrderAwarenessDataInputStream dataInputStream,
1466            int dataFormat, int numberOfComponents) throws IOException {
1467        // See TIFF 6.0 spec Types. page 15.
1468        switch (dataFormat) {
1469            case IFD_FORMAT_BYTE: {
1470                return String.valueOf(dataInputStream.readByte());
1471            }
1472            case IFD_FORMAT_SBYTE: {
1473                return String.valueOf(dataInputStream.readByte() & 0xff);
1474            }
1475            case IFD_FORMAT_USHORT: {
1476                return String.valueOf(dataInputStream.readUnsignedShort());
1477            }
1478            case IFD_FORMAT_SSHORT: {
1479                return String.valueOf(dataInputStream.readUnsignedInt());
1480            }
1481            case IFD_FORMAT_ULONG: {
1482                return String.valueOf(dataInputStream.readInt());
1483            }
1484            case IFD_FORMAT_SLONG: {
1485                return String.valueOf(dataInputStream.readInt());
1486            }
1487            case IFD_FORMAT_URATIONAL:
1488            case IFD_FORMAT_SRATIONAL: {
1489                int numerator = dataInputStream.readInt();
1490                int denominator = dataInputStream.readInt();
1491                return numerator + "/" + denominator;
1492            }
1493            case IFD_FORMAT_SINGLE: {
1494                return String.valueOf(dataInputStream.readFloat());
1495            }
1496            case IFD_FORMAT_DOUBLE: {
1497                return String.valueOf(dataInputStream.readDouble());
1498            }
1499            case IFD_FORMAT_UNDEFINED:  // Usually UNDEFINED format is ASCII.
1500            case IFD_FORMAT_STRING: {
1501                byte[] bytes = new byte[numberOfComponents];
1502                dataInputStream.readFully(bytes);
1503                int index = 0;
1504                if (numberOfComponents >= EXIF_ASCII_PREFIX.length) {
1505                    boolean same = true;
1506                    for (int i = 0; i < EXIF_ASCII_PREFIX.length; ++i) {
1507                        if (bytes[i] != EXIF_ASCII_PREFIX[i]) {
1508                            same = false;
1509                            break;
1510                        }
1511                    }
1512                    if (same) {
1513                        index = EXIF_ASCII_PREFIX.length;
1514                    }
1515                }
1516
1517                StringBuilder stringBuilder = new StringBuilder();
1518                while (true) {
1519                    int ch = bytes[index];
1520                    if (ch < 0)
1521                        throw new EOFException();
1522                    if (ch == 0)
1523                        break;
1524                    if (ch >= 32)
1525                        stringBuilder.append((char) ch);
1526                    else
1527                        stringBuilder.append('?');
1528                    ++index;
1529                    if (index == numberOfComponents)
1530                        break;
1531                }
1532                return stringBuilder.toString();
1533            }
1534            default: {
1535                // Nothing to do
1536                return null;
1537            }
1538        }
1539    }
1540
1541    // Gets the corresponding IFD group index of the given tag number for writing Exif Tags.
1542    private static int getIfdHintFromTagNumber(int tagNumber) {
1543        for (int i = 0; i < IFD_POINTER_TAG_HINTS.length; ++i) {
1544            if (IFD_POINTER_TAGS[i].number == tagNumber)
1545                return IFD_POINTER_TAG_HINTS[i];
1546        }
1547        return -1;
1548    }
1549
1550    // Writes an Exif segment into the given output stream.
1551    private int writeExifSegment(ExifDataOutputStream dataOutputStream, int exifOffsetFromBeginning)
1552            throws IOException {
1553        // The following variables are for calculating each IFD tag group size in bytes.
1554        int[] ifdOffsets = new int[EXIF_TAGS.length];
1555        int[] ifdDataSizes = new int[EXIF_TAGS.length];
1556
1557        // Maps to store tags per IFD tag group
1558        HashMap[] ifdTags = new HashMap[EXIF_TAGS.length];
1559        for (int i = 0; i < EXIF_TAGS.length; ++i) {
1560            ifdTags[i] = new HashMap();
1561        }
1562
1563        // Remove IFD pointer tags (we'll re-add it later.)
1564        for (ExifTag tag : IFD_POINTER_TAGS) {
1565            mAttributes.remove(tag.name);
1566        }
1567
1568        // Assign tags to the corresponding group
1569        for (Map.Entry<String, String> entry : mAttributes.entrySet()) {
1570            Pair<Integer, Integer> pair = sExifTagMapForWriting.get(entry.getKey());
1571            if (pair != null) {
1572                int tagNumber = pair.first;
1573                int hint = pair.second;
1574                ifdTags[hint].put(tagNumber, entry.getValue());
1575            }
1576        }
1577
1578        // Add IFD pointer tags. The next offset of primary image TIFF IFD will have thumbnail IFD
1579        // offset when there is one or more tags in the thumbnail IFD.
1580        if (!ifdTags[IFD_INTEROPERABILITY_HINT].isEmpty()) {
1581            ifdTags[IFD_EXIF_HINT].put(IFD_POINTER_TAGS[2].number, "0");
1582        }
1583        if (!ifdTags[IFD_EXIF_HINT].isEmpty()) {
1584            ifdTags[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[0].number, "0");
1585        }
1586        if (!ifdTags[IFD_GPS_HINT].isEmpty()) {
1587            ifdTags[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[1].number, "0");
1588        }
1589        if (mHasThumbnail) {
1590            ifdTags[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_TAG.number, "0");
1591            ifdTags[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.number,
1592                    String.valueOf(mThumbnailLength));
1593        }
1594
1595        // Calculate IFD group data area sizes. IFD group data area is assigned to save the entry
1596        // value which has a bigger size than 4 bytes.
1597        for (int i = 0; i < 5; ++i) {
1598            int sum = 0;
1599            for (Object entry : ifdTags[i].entrySet()) {
1600                String entryValue = (String) ((Map.Entry) entry).getValue();
1601                int dataFormat = getDataFormatOfExifEntryValue(entryValue);
1602                int size = getSizeOfExifEntryValue(dataFormat, entryValue);
1603                if (size > 4) {
1604                    sum += size;
1605                }
1606            }
1607            ifdDataSizes[i] += sum;
1608        }
1609
1610        // Calculate IFD offsets.
1611        int position = 8;
1612        for (int hint = 0; hint < EXIF_TAGS.length; ++hint) {
1613            if (!ifdTags[hint].isEmpty()) {
1614                ifdOffsets[hint] = position;
1615                position += 2 + ifdTags[hint].size() * 12 + 4 + ifdDataSizes[hint];
1616            }
1617        }
1618        if (mHasThumbnail) {
1619            int thumbnailOffset = position;
1620            ifdTags[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_TAG.number,
1621                    String.valueOf(thumbnailOffset));
1622            ifdTags[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.number,
1623                    String.valueOf(mThumbnailLength));
1624            mThumbnailOffset = exifOffsetFromBeginning + thumbnailOffset;
1625            position += mThumbnailLength;
1626        }
1627
1628        // Calculate the total size
1629        int totalSize = position + 8;  // eight bytes is for header part.
1630        if (DEBUG) {
1631            Log.d(TAG, "totalSize length: " + totalSize);
1632            for (int i = 0; i < 5; ++i) {
1633                Log.d(TAG, String.format("index: %d, offsets: %d, tag count: %d, data sizes: %d",
1634                        i, ifdOffsets[i], ifdTags[i].size(), ifdDataSizes[i]));
1635            }
1636        }
1637
1638        // Update IFD pointer tags with the calculated offsets.
1639        if (!ifdTags[IFD_EXIF_HINT].isEmpty()) {
1640            ifdTags[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[0].number,
1641                    String.valueOf(ifdOffsets[IFD_EXIF_HINT]));
1642        }
1643        if (!ifdTags[IFD_GPS_HINT].isEmpty()) {
1644            ifdTags[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[1].number,
1645                    String.valueOf(ifdOffsets[IFD_GPS_HINT]));
1646        }
1647        if (!ifdTags[IFD_INTEROPERABILITY_HINT].isEmpty()) {
1648            ifdTags[IFD_EXIF_HINT].put(IFD_POINTER_TAGS[2].number,
1649                    String.valueOf(ifdOffsets[IFD_INTEROPERABILITY_HINT]));
1650        }
1651
1652        // Write TIFF Headers. See JEITA CP-3451C Table 1. page 10.
1653        dataOutputStream.writeUnsignedShort(totalSize);
1654        dataOutputStream.write(IDENTIFIER_APP1);
1655        dataOutputStream.writeShort(BYTE_ALIGN_MM);
1656        dataOutputStream.writeUnsignedShort(0x2a);
1657        dataOutputStream.writeUnsignedInt(8);
1658
1659        // Write IFD groups. See JEITA CP-3451C Figure 7. page 12.
1660        for (int hint = 0; hint < EXIF_TAGS.length; ++hint) {
1661            if (!ifdTags[hint].isEmpty()) {
1662                // See JEITA CP-3451C 4.6.2 IFD structure. page 13.
1663                // Write entry count
1664                dataOutputStream.writeUnsignedShort(ifdTags[hint].size());
1665
1666                // Write entry info
1667                int dataOffset = ifdOffsets[hint] + 2 + ifdTags[hint].size() * 12 + 4;
1668                for (Object obj : ifdTags[hint].entrySet()) {
1669                    Map.Entry entry = (Map.Entry) obj;
1670                    int tagNumber = (int) entry.getKey();
1671                    String entryValue = (String) entry.getValue();
1672
1673                    int dataFormat = getDataFormatOfExifEntryValue(entryValue);
1674                    int numberOfComponents = getNumberOfComponentsInExifEntryValue(dataFormat,
1675                            entryValue);
1676                    int byteCount = getSizeOfExifEntryValue(dataFormat, entryValue);
1677
1678                    dataOutputStream.writeUnsignedShort(tagNumber);
1679                    dataOutputStream.writeUnsignedShort(dataFormat);
1680                    dataOutputStream.writeInt(numberOfComponents);
1681                    if (byteCount > 4) {
1682                        dataOutputStream.writeUnsignedInt(dataOffset);
1683                        dataOffset += byteCount;
1684                    } else {
1685                        int bytesWritten = writeExifEntryValue(dataOutputStream, entryValue);
1686                        // Fill zero up to 4 bytes
1687                        if (bytesWritten < 4) {
1688                            for (int i = bytesWritten; i < 4; ++i) {
1689                                dataOutputStream.write(0);
1690                            }
1691                        }
1692                    }
1693                }
1694
1695                // Write the next offset. It writes the offset of thumbnail IFD if there is one or
1696                // more tags in the thumbnail IFD when the current IFD is the primary image TIFF
1697                // IFD; Otherwise 0.
1698                if (hint == 0 && !ifdTags[IFD_THUMBNAIL_HINT].isEmpty()) {
1699                    dataOutputStream.writeUnsignedInt(ifdOffsets[IFD_THUMBNAIL_HINT]);
1700                } else {
1701                    dataOutputStream.writeUnsignedInt(0);
1702                }
1703
1704                // Write values of data field exceeding 4 bytes after the next offset.
1705                for (Object obj : ifdTags[hint].entrySet()) {
1706                    Map.Entry entry = (Map.Entry) obj;
1707                    String entryValue = (String) entry.getValue();
1708
1709                    int dataFormat = getDataFormatOfExifEntryValue(entryValue);
1710                    int byteCount = getSizeOfExifEntryValue(dataFormat, entryValue);
1711                    if (byteCount > 4) {
1712                        writeExifEntryValue(dataOutputStream, entryValue);
1713                    }
1714                }
1715            }
1716        }
1717
1718        // Write thumbnail
1719        if (mHasThumbnail) {
1720            dataOutputStream.write(getThumbnail());
1721        }
1722
1723        return totalSize;
1724    }
1725
1726    // Writes EXIF entry value and its entry value type will be automatically determined.
1727    private static int writeExifEntryValue(ExifDataOutputStream dataOutputStream, String entryValue)
1728            throws IOException {
1729        int bytesWritten = 0;
1730        int dataFormat = getDataFormatOfExifEntryValue(entryValue);
1731
1732        // Values can be composed of several components. Each component is separated by char ','.
1733        String[] components = entryValue.split(",");
1734        for (String component : components) {
1735            switch (dataFormat) {
1736                case IFD_FORMAT_SLONG:
1737                    dataOutputStream.writeInt(Integer.parseInt(component));
1738                    bytesWritten += 4;
1739                    break;
1740                case IFD_FORMAT_DOUBLE:
1741                    dataOutputStream.writeDouble(Double.parseDouble(component));
1742                    bytesWritten += 8;
1743                    break;
1744                case IFD_FORMAT_STRING:
1745                    byte[] asciiArray = (component + '\0').getBytes(Charset.forName("US-ASCII"));
1746                    dataOutputStream.write(asciiArray);
1747                    bytesWritten += asciiArray.length;
1748                    break;
1749                case IFD_FORMAT_SRATIONAL:
1750                    String[] rationalNumber = component.split("/");
1751                    dataOutputStream.writeInt(Integer.parseInt(rationalNumber[0]));
1752                    dataOutputStream.writeInt(Integer.parseInt(rationalNumber[1]));
1753                    bytesWritten += 8;
1754                    break;
1755                default:
1756                    throw new IllegalArgumentException();
1757            }
1758        }
1759        return bytesWritten;
1760    }
1761
1762    // Determines the data format of EXIF entry value.
1763    private static int getDataFormatOfExifEntryValue(String entryValue) {
1764        // See TIFF 6.0 spec Types. page 15.
1765        // Take the first component if there are more than one component.
1766        if (entryValue.contains(",")) {
1767            entryValue = entryValue.split(",")[0];
1768        }
1769
1770        if (entryValue.contains("/")) {
1771            return IFD_FORMAT_SRATIONAL;
1772        }
1773        try {
1774            Integer.parseInt(entryValue);
1775            return IFD_FORMAT_SLONG;
1776        } catch (NumberFormatException e) {
1777            // Ignored
1778        }
1779        try {
1780            Double.parseDouble(entryValue);
1781            return IFD_FORMAT_DOUBLE;
1782        } catch (NumberFormatException e) {
1783            // Ignored
1784        }
1785        return IFD_FORMAT_STRING;
1786    }
1787
1788    // Determines the size of EXIF entry value.
1789    private static int getSizeOfExifEntryValue(int dataFormat, String entryValue) {
1790        // See TIFF 6.0 spec Types page 15.
1791        int bytesEstimated = 0;
1792        String[] components = entryValue.split(",");
1793        for (String component : components) {
1794            switch (dataFormat) {
1795                case IFD_FORMAT_SLONG:
1796                    bytesEstimated += 4;
1797                    break;
1798                case IFD_FORMAT_DOUBLE:
1799                    bytesEstimated += 8;
1800                    break;
1801                case IFD_FORMAT_STRING:
1802                    bytesEstimated
1803                            += (component + '\0').getBytes(Charset.forName("US-ASCII")).length;
1804                    break;
1805                case IFD_FORMAT_SRATIONAL:
1806                    bytesEstimated += 8;
1807                    break;
1808                default:
1809                    throw new IllegalArgumentException();
1810            }
1811        }
1812        return bytesEstimated;
1813    }
1814
1815    // Determines the number of components of EXIF entry value.
1816    private static int getNumberOfComponentsInExifEntryValue(int dataFormat, String entryValue) {
1817        if (dataFormat == IFD_FORMAT_STRING) {
1818            return (entryValue + '\0').getBytes(Charset.forName("US-ASCII")).length;
1819        }
1820        int count = 1;
1821        for (int i = 0; i < entryValue.length(); ++i) {
1822            if (entryValue.charAt(i) == ',') {
1823                ++count;
1824            }
1825        }
1826        return count;
1827    }
1828
1829    // An input stream to parse EXIF data area, which can be written in either little or big endian
1830    // order.
1831    private static class ByteOrderAwarenessDataInputStream extends ByteArrayInputStream {
1832        private static final ByteOrder LITTLE_ENDIAN = ByteOrder.LITTLE_ENDIAN;
1833        private static final ByteOrder BIG_ENDIAN = ByteOrder.BIG_ENDIAN;
1834
1835        private ByteOrder mByteOrder = ByteOrder.BIG_ENDIAN;
1836        private final long mLength;
1837        private long mPosition;
1838
1839        public ByteOrderAwarenessDataInputStream(byte[] bytes) {
1840            super(bytes);
1841            mLength = bytes.length;
1842            mPosition = 0L;
1843        }
1844
1845        public void setByteOrder(ByteOrder byteOrder) {
1846            mByteOrder = byteOrder;
1847        }
1848
1849        public void seek(long byteCount) throws IOException {
1850            mPosition = 0L;
1851            reset();
1852            if (skip(byteCount) != byteCount)
1853                throw new IOException("Couldn't seek up to the byteCount");
1854        }
1855
1856        public long peek() {
1857            return mPosition;
1858        }
1859
1860        public void readFully(byte[] buffer) throws IOException {
1861            mPosition += buffer.length;
1862            if (mPosition > mLength)
1863                throw new EOFException();
1864            if (super.read(buffer, 0, buffer.length) != buffer.length) {
1865                throw new IOException("Couldn't read up to the length of buffer");
1866            }
1867        }
1868
1869        public byte readByte() throws IOException {
1870            ++mPosition;
1871            if (mPosition > mLength)
1872                throw new EOFException();
1873            int ch = super.read();
1874            if (ch < 0)
1875                throw new EOFException();
1876            return (byte) ch;
1877        }
1878
1879        public short readShort() throws IOException {
1880            mPosition += 2;
1881            if (mPosition > mLength)
1882                throw new EOFException();
1883            int ch1 = super.read();
1884            int ch2 = super.read();
1885            if ((ch1 | ch2) < 0)
1886                throw new EOFException();
1887            if (mByteOrder == LITTLE_ENDIAN) {
1888                return (short) ((ch2 << 8) + (ch1));
1889            } else if (mByteOrder == BIG_ENDIAN) {
1890                return (short) ((ch1 << 8) + (ch2));
1891            }
1892            throw new IOException("Invalid byte order: " + mByteOrder);
1893        }
1894
1895        public int readInt() throws IOException {
1896            mPosition += 4;
1897            if (mPosition > mLength)
1898                throw new EOFException();
1899            int ch1 = super.read();
1900            int ch2 = super.read();
1901            int ch3 = super.read();
1902            int ch4 = super.read();
1903            if ((ch1 | ch2 | ch3 | ch4) < 0)
1904                throw new EOFException();
1905            if (mByteOrder == LITTLE_ENDIAN) {
1906                return ((ch4 << 24) + (ch3 << 16) + (ch2 << 8) + ch1);
1907            } else if (mByteOrder == BIG_ENDIAN) {
1908                return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4);
1909            }
1910            throw new IOException("Invalid byte order: " + mByteOrder);
1911        }
1912
1913        @Override
1914        public long skip(long byteCount) {
1915            long skipped = super.skip(Math.min(byteCount, mLength - mPosition));
1916            mPosition += skipped;
1917            return skipped;
1918        }
1919
1920        public int readUnsignedShort() throws IOException {
1921            mPosition += 2;
1922            if (mPosition > mLength)
1923                throw new EOFException();
1924            int ch1 = super.read();
1925            int ch2 = super.read();
1926            if ((ch1 | ch2) < 0)
1927                throw new EOFException();
1928            if (mByteOrder == LITTLE_ENDIAN) {
1929                return ((ch2 << 8) + (ch1));
1930            } else if (mByteOrder == BIG_ENDIAN) {
1931                return ((ch1 << 8) + (ch2));
1932            }
1933            throw new IOException("Invalid byte order: " + mByteOrder);
1934        }
1935
1936        public long readUnsignedInt() throws IOException {
1937            return readInt() & 0xffffffffL;
1938        }
1939
1940        public long readLong() throws IOException {
1941            mPosition += 8;
1942            if (mPosition > mLength)
1943                throw new EOFException();
1944            int ch1 = super.read();
1945            int ch2 = super.read();
1946            int ch3 = super.read();
1947            int ch4 = super.read();
1948            int ch5 = super.read();
1949            int ch6 = super.read();
1950            int ch7 = super.read();
1951            int ch8 = super.read();
1952            if ((ch1 | ch2 | ch3 | ch4 | ch5 | ch6 | ch7 | ch8) < 0)
1953                throw new EOFException();
1954            if (mByteOrder == LITTLE_ENDIAN) {
1955                return (((long) ch8 << 56) + ((long) ch7 << 48) + ((long) ch6 << 40)
1956                        + ((long) ch5 << 32) + ((long) ch4 << 24) + ((long) ch3 << 16)
1957                        + ((long) ch2 << 8) + (long) ch1);
1958            } else if (mByteOrder == BIG_ENDIAN) {
1959                return (((long) ch1 << 56) + ((long) ch2 << 48) + ((long) ch3 << 40)
1960                        + ((long) ch4 << 32) + ((long) ch5 << 24) + ((long) ch6 << 16)
1961                        + ((long) ch7 << 8) + (long) ch8);
1962            }
1963            throw new IOException("Invalid byte order: " + mByteOrder);
1964        }
1965
1966        public float readFloat() throws IOException {
1967            return Float.intBitsToFloat(readInt());
1968        }
1969
1970        public double readDouble() throws IOException {
1971            return Double.longBitsToDouble(readLong());
1972        }
1973    }
1974
1975    // An output stream to write EXIF data area, that will be written in big endian byte order.
1976    private static class ExifDataOutputStream extends DataOutputStream {
1977        public ExifDataOutputStream(OutputStream out) {
1978            super(out);
1979        }
1980
1981        public void writeUnsignedShort(int val) throws IOException {
1982            writeShort((short) val);
1983        }
1984
1985        public void writeUnsignedInt(long val) throws IOException {
1986            writeInt((int) val);
1987        }
1988    }
1989
1990    // JNI methods for RAW formats.
1991    private static native void initRawNative();
1992    private static native HashMap getRawAttributesNative(FileDescriptor fileDescriptor);
1993}
1994