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