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