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