ExifInterface.java revision 42ac0d00153cf8cd4c89cb9397c937be4e8f36e3
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 boolean mIsRaw;
655    private final HashMap[] mAttributes = new HashMap[EXIF_TAGS.length];
656    private boolean mHasThumbnail;
657    // The following values used for indicating a thumbnail position.
658    private int mThumbnailOffset;
659    private int mThumbnailLength;
660    private byte[] mThumbnailBytes;
661
662    // Pattern to check non zero timestamp
663    private static final Pattern sNonZeroTimePattern = Pattern.compile(".*[1-9].*");
664
665    /**
666     * Reads Exif tags from the specified image file.
667     */
668    public ExifInterface(String filename) throws IOException {
669        if (filename == null) {
670            throw new IllegalArgumentException("filename cannot be null");
671        }
672        FileInputStream in = new FileInputStream(filename);
673        mAssetInputStream = null;
674        mFilename = filename;
675        if (isSeekableFD(in.getFD())) {
676            mSeekableFileDescriptor = in.getFD();
677        } else {
678            mSeekableFileDescriptor = null;
679        }
680        loadAttributes(in);
681    }
682
683    /**
684     * Reads Exif tags from the specified image file descriptor. Attribute mutation is supported
685     * for seekable file descriptors only.
686     */
687    public ExifInterface(FileDescriptor fileDescriptor) throws IOException {
688        if (fileDescriptor == null) {
689            throw new IllegalArgumentException("parcelFileDescriptor cannot be null");
690        }
691        mAssetInputStream = null;
692        mFilename = null;
693        if (isSeekableFD(fileDescriptor)) {
694            mSeekableFileDescriptor = fileDescriptor;
695        } else {
696            mSeekableFileDescriptor = null;
697        }
698        loadAttributes(new FileInputStream(fileDescriptor));
699    }
700
701    /**
702     * Reads Exif tags from the specified image input stream. Attribute mutation is not supported
703     * for input streams.
704     */
705    public ExifInterface(InputStream inputStream) throws IOException {
706        if (inputStream == null) {
707            throw new IllegalArgumentException("inputStream cannot be null");
708        }
709        mFilename = null;
710        if (inputStream instanceof AssetManager.AssetInputStream) {
711            mAssetInputStream = (AssetManager.AssetInputStream) inputStream;
712            mSeekableFileDescriptor = null;
713        } else if (inputStream instanceof FileInputStream
714                && isSeekableFD(((FileInputStream) inputStream).getFD())) {
715            mAssetInputStream = null;
716            mSeekableFileDescriptor = ((FileInputStream) inputStream).getFD();
717        } else {
718            mAssetInputStream = null;
719            mSeekableFileDescriptor = null;
720        }
721        loadAttributes(inputStream);
722    }
723
724    /**
725     * Returns the value of the specified tag or {@code null} if there
726     * is no such tag in the image file.
727     *
728     * @param tag the name of the tag.
729     */
730    public String getAttribute(String tag) {
731        // Retrieves all tag groups. The value from primary image tag group has a higher priority
732        // than the value from the thumbnail tag group if there are more than one candidates.
733        for (int i = 0; i < EXIF_TAGS.length; ++i) {
734            Object value = mAttributes[i].get(tag);
735            if (value != null) {
736                return (String) value;
737            }
738        }
739        return null;
740    }
741
742    /**
743     * Returns the integer value of the specified tag. If there is no such tag
744     * in the image file or the value cannot be parsed as integer, return
745     * <var>defaultValue</var>.
746     *
747     * @param tag the name of the tag.
748     * @param defaultValue the value to return if the tag is not available.
749     */
750    public int getAttributeInt(String tag, int defaultValue) {
751        String value = getAttribute(tag);
752        if (value == null) return defaultValue;
753        try {
754            return Integer.valueOf(value);
755        } catch (NumberFormatException e) {
756            return defaultValue;
757        }
758    }
759
760    /**
761     * Returns the double value of the tag that is specified as rational or contains a
762     * double-formatted value. If there is no such tag in the image file or the value cannot be
763     * parsed as double, return <var>defaultValue</var>.
764     *
765     * @param tag the name of the tag.
766     * @param defaultValue the value to return if the tag is not available.
767     */
768    public double getAttributeDouble(String tag, double defaultValue) {
769        String value = getAttribute(tag);
770        if (value == null) return defaultValue;
771        try {
772            int index = value.indexOf("/");
773            if (index == -1) return Double.parseDouble(value);
774            double denom = Double.parseDouble(value.substring(index + 1));
775            if (denom == 0) return defaultValue;
776            double num = Double.parseDouble(value.substring(0, index));
777            return num / denom;
778        } catch (NumberFormatException e) {
779            return defaultValue;
780        }
781    }
782
783    /**
784     * Set the value of the specified tag.
785     *
786     * @param tag the name of the tag.
787     * @param value the value of the tag.
788     */
789    public void setAttribute(String tag, String value) {
790        for (int i = 0 ; i < EXIF_TAGS.length; ++i) {
791            if (sExifTagMapsForWriting[i].containsKey(tag)) {
792                mAttributes[i].put(tag, value);
793            }
794        }
795    }
796
797    /**
798     * This function decides which parser to read the image data according to the given input stream
799     * type and the content of the input stream. In each case, it reads the first three bytes to
800     * determine whether the image data format is JPEG or not.
801     */
802    private void loadAttributes(@NonNull InputStream in) throws IOException {
803        // Initialize mAttributes.
804        for (int i = 0; i < EXIF_TAGS.length; ++i) {
805            mAttributes[i] = new HashMap();
806        }
807
808        // Process RAW input stream
809        if (mAssetInputStream != null) {
810            long asset = mAssetInputStream.getNativeAsset();
811            if (handleRawResult(nativeGetRawAttributesFromAsset(asset))) {
812                return;
813            }
814        } else if (mSeekableFileDescriptor != null) {
815            if (handleRawResult(nativeGetRawAttributesFromFileDescriptor(
816                    mSeekableFileDescriptor))) {
817                return;
818            }
819        } else {
820            in = new BufferedInputStream(in, JPEG_SIGNATURE_SIZE);
821            if (!isJpegInputStream((BufferedInputStream) in) && handleRawResult(
822                    nativeGetRawAttributesFromInputStream(in))) {
823                return;
824            }
825        }
826
827        // Process JPEG input stream
828        try {
829            getJpegAttributes(in);
830        } catch (IOException e) {
831            // Ignore exceptions in order to keep the compatibility with the old versions of
832            // ExifInterface.
833            Log.w(TAG, "Invalid JPEG: ExifInterface got an unsupported image format file"
834                    + "(ExifInterface supports JPEG and some RAW image formats only) "
835                    + "or a corrupted JPEG file to ExifInterface.", e);
836        }
837
838        if (DEBUG) {
839            printAttributes();
840        }
841    }
842
843    private static boolean isJpegInputStream(BufferedInputStream in) throws IOException {
844        in.mark(JPEG_SIGNATURE_SIZE);
845        byte[] signatureBytes = new byte[JPEG_SIGNATURE_SIZE];
846        if (in.read(signatureBytes) != JPEG_SIGNATURE_SIZE) {
847            throw new EOFException();
848        }
849        boolean isJpeg = Arrays.equals(JPEG_SIGNATURE, signatureBytes);
850        in.reset();
851        return isJpeg;
852    }
853
854    private boolean handleRawResult(HashMap map) {
855        if (map == null) {
856            return false;
857        }
858
859        // Mark for disabling the save feature.
860        mIsRaw = true;
861
862        for (Map.Entry entry : (Set<Map.Entry>) map.entrySet()) {
863            String attrName = (String) entry.getKey();
864
865            switch (attrName) {
866                case TAG_HAS_THUMBNAIL:
867                    mHasThumbnail = ((String) entry.getValue()).equalsIgnoreCase("true");
868                    break;
869                case TAG_THUMBNAIL_OFFSET:
870                    mThumbnailOffset = Integer.parseInt((String) entry.getValue());
871                    break;
872                case TAG_THUMBNAIL_LENGTH:
873                    mThumbnailLength = Integer.parseInt((String) entry.getValue());
874                    break;
875                case TAG_THUMBNAIL_DATA:
876                    mThumbnailBytes = (byte[]) entry.getValue();
877                    break;
878                default:
879                    setAttribute(attrName, (String) entry.getValue());
880                    break;
881            }
882        }
883
884        if (DEBUG) {
885            printAttributes();
886        }
887        return true;
888    }
889
890    private static boolean isSeekableFD(FileDescriptor fd) throws IOException {
891        try {
892            Os.lseek(fd, 0, OsConstants.SEEK_CUR);
893            return true;
894        } catch (ErrnoException e) {
895            return false;
896        }
897    }
898
899    // Prints out attributes for debugging.
900    private void printAttributes() {
901        for (int i = 0; i < mAttributes.length; ++i) {
902            Log.d(TAG, "The size of tag group[" + i + "]: " + mAttributes[i].size());
903            for (Map.Entry entry : (Set<Map.Entry>) mAttributes[i].entrySet()) {
904                Log.d(TAG, "tagName: " + entry.getKey() + ", tagValue: " + entry.getValue());
905            }
906        }
907    }
908
909    /**
910     * Save the tag data into the original image file. This is expensive because it involves
911     * copying all the data from one file to another and deleting the old file and renaming the
912     * other. It's best to use{@link #setAttribute(String,String)} to set all attributes to write
913     * and make a single call rather than multiple calls for each attribute.
914     */
915    public void saveAttributes() throws IOException {
916        if (mIsRaw) {
917            throw new UnsupportedOperationException(
918                    "ExifInterface does not support saving attributes on RAW formats.");
919        }
920        if (mSeekableFileDescriptor == null && mFilename == null) {
921            throw new UnsupportedOperationException(
922                    "ExifInterface does not support saving attributes for the current input.");
923        }
924
925        // Keep the thumbnail in memory
926        mThumbnailBytes = getThumbnail();
927
928        FileInputStream in = null;
929        FileOutputStream out = null;
930        File tempFile = null;
931        try {
932            // Move the original file to temporary file.
933            if (mFilename != null) {
934                tempFile = new File(mFilename + ".tmp");
935                File originalFile = new File(mFilename);
936                if (!originalFile.renameTo(tempFile)) {
937                    throw new IOException("Could'nt rename to " + tempFile.getAbsolutePath());
938                }
939            } else if (mSeekableFileDescriptor != null) {
940                tempFile = File.createTempFile("temp", "jpg");
941                Os.lseek(mSeekableFileDescriptor, 0, OsConstants.SEEK_SET);
942                in = new FileInputStream(mSeekableFileDescriptor);
943                out = new FileOutputStream(tempFile);
944                Streams.copy(in, out);
945            }
946        } catch (ErrnoException e) {
947            e.rethrowAsIOException();
948        } finally {
949            IoUtils.closeQuietly(in);
950            IoUtils.closeQuietly(out);
951        }
952
953        in = null;
954        out = null;
955        try {
956            // Save the new file.
957            in = new FileInputStream(tempFile);
958            if (mFilename != null) {
959                out = new FileOutputStream(mFilename);
960            } else if (mSeekableFileDescriptor != null) {
961                Os.lseek(mSeekableFileDescriptor, 0, OsConstants.SEEK_SET);
962                out = new FileOutputStream(mSeekableFileDescriptor);
963            }
964            saveJpegAttributes(in, out);
965        } catch (ErrnoException e) {
966            e.rethrowAsIOException();
967        } finally {
968            IoUtils.closeQuietly(in);
969            IoUtils.closeQuietly(out);
970            tempFile.delete();
971        }
972
973        // Discard the thumbnail in memory
974        mThumbnailBytes = null;
975    }
976
977    /**
978     * Returns true if the image file has a thumbnail.
979     */
980    public boolean hasThumbnail() {
981        return mHasThumbnail;
982    }
983
984    /**
985     * Returns the thumbnail inside the image file, or {@code null} if there is no thumbnail.
986     * The returned data is in JPEG format and can be decoded using
987     * {@link android.graphics.BitmapFactory#decodeByteArray(byte[],int,int)}
988     */
989    public byte[] getThumbnail() {
990        if (!mHasThumbnail) {
991            return null;
992        }
993        if (mThumbnailBytes != null) {
994            return mThumbnailBytes;
995        }
996
997        // Read the thumbnail.
998        FileInputStream in = null;
999        try {
1000            if (mAssetInputStream != null) {
1001                return nativeGetThumbnailFromAsset(
1002                        mAssetInputStream.getNativeAsset(), mThumbnailOffset, mThumbnailLength);
1003            } else if (mFilename != null) {
1004                in = new FileInputStream(mFilename);
1005            } else if (mSeekableFileDescriptor != null) {
1006                Os.lseek(mSeekableFileDescriptor, 0, OsConstants.SEEK_SET);
1007                in = new FileInputStream(mSeekableFileDescriptor);
1008            }
1009            if (in == null) {
1010                // Should not be reached this.
1011                throw new FileNotFoundException();
1012            }
1013            if (in.skip(mThumbnailOffset) != mThumbnailOffset) {
1014                throw new IOException("Corrupted image");
1015            }
1016            byte[] buffer = new byte[mThumbnailLength];
1017            if (in.read(buffer) != mThumbnailLength) {
1018                throw new IOException("Corrupted image");
1019            }
1020            return buffer;
1021        } catch (IOException | ErrnoException e) {
1022            // Couldn't get a thumbnail image.
1023        } finally {
1024            IoUtils.closeQuietly(in);
1025        }
1026        return null;
1027    }
1028
1029    /**
1030     * Returns the offset and length of thumbnail inside the image file, or
1031     * {@code null} if there is no thumbnail.
1032     *
1033     * @return two-element array, the offset in the first value, and length in
1034     *         the second, or {@code null} if no thumbnail was found.
1035     * @hide
1036     */
1037    public long[] getThumbnailRange() {
1038        long[] range = new long[2];
1039        range[0] = mThumbnailOffset;
1040        range[1] = mThumbnailLength;
1041        return range;
1042    }
1043
1044    /**
1045     * Stores the latitude and longitude value in a float array. The first element is
1046     * the latitude, and the second element is the longitude. Returns false if the
1047     * Exif tags are not available.
1048     */
1049    public boolean getLatLong(float output[]) {
1050        String latValue = getAttribute(TAG_GPS_LATITUDE);
1051        String latRef = getAttribute(TAG_GPS_LATITUDE_REF);
1052        String lngValue = getAttribute(TAG_GPS_LONGITUDE);
1053        String lngRef = getAttribute(TAG_GPS_LONGITUDE_REF);
1054
1055        if (latValue != null && latRef != null && lngValue != null && lngRef != null) {
1056            try {
1057                output[0] = convertRationalLatLonToFloat(latValue, latRef);
1058                output[1] = convertRationalLatLonToFloat(lngValue, lngRef);
1059                return true;
1060            } catch (IllegalArgumentException e) {
1061                // if values are not parseable
1062            }
1063        }
1064
1065        return false;
1066    }
1067
1068    /**
1069     * Return the altitude in meters. If the exif tag does not exist, return
1070     * <var>defaultValue</var>.
1071     *
1072     * @param defaultValue the value to return if the tag is not available.
1073     */
1074    public double getAltitude(double defaultValue) {
1075        double altitude = getAttributeDouble(TAG_GPS_ALTITUDE, -1);
1076        int ref = getAttributeInt(TAG_GPS_ALTITUDE_REF, -1);
1077
1078        if (altitude >= 0 && ref >= 0) {
1079            return (altitude * ((ref == 1) ? -1 : 1));
1080        } else {
1081            return defaultValue;
1082        }
1083    }
1084
1085    /**
1086     * Returns number of milliseconds since Jan. 1, 1970, midnight local time.
1087     * Returns -1 if the date time information if not available.
1088     * @hide
1089     */
1090    public long getDateTime() {
1091        String dateTimeString = getAttribute(TAG_DATETIME);
1092        if (dateTimeString == null
1093                || !sNonZeroTimePattern.matcher(dateTimeString).matches()) return -1;
1094
1095        ParsePosition pos = new ParsePosition(0);
1096        try {
1097            // The exif field is in local time. Parsing it as if it is UTC will yield time
1098            // since 1/1/1970 local time
1099            Date datetime = sFormatter.parse(dateTimeString, pos);
1100            if (datetime == null) return -1;
1101            long msecs = datetime.getTime();
1102
1103            String subSecs = getAttribute(TAG_SUBSECTIME);
1104            if (subSecs != null) {
1105                try {
1106                    long sub = Long.valueOf(subSecs);
1107                    while (sub > 1000) {
1108                        sub /= 10;
1109                    }
1110                    msecs += sub;
1111                } catch (NumberFormatException e) {
1112                    // Ignored
1113                }
1114            }
1115            return msecs;
1116        } catch (IllegalArgumentException e) {
1117            return -1;
1118        }
1119    }
1120
1121    /**
1122     * Returns number of milliseconds since Jan. 1, 1970, midnight UTC.
1123     * Returns -1 if the date time information if not available.
1124     * @hide
1125     */
1126    public long getGpsDateTime() {
1127        String date = getAttribute(TAG_GPS_DATESTAMP);
1128        String time = getAttribute(TAG_GPS_TIMESTAMP);
1129        if (date == null || time == null
1130                || (!sNonZeroTimePattern.matcher(date).matches()
1131                && !sNonZeroTimePattern.matcher(time).matches())) {
1132            return -1;
1133        }
1134
1135        String dateTimeString = date + ' ' + time;
1136
1137        ParsePosition pos = new ParsePosition(0);
1138        try {
1139            Date datetime = sFormatter.parse(dateTimeString, pos);
1140            if (datetime == null) return -1;
1141            return datetime.getTime();
1142        } catch (IllegalArgumentException e) {
1143            return -1;
1144        }
1145    }
1146
1147    private static float convertRationalLatLonToFloat(String rationalString, String ref) {
1148        try {
1149            String [] parts = rationalString.split(",");
1150
1151            String [] pair;
1152            pair = parts[0].split("/");
1153            double degrees = Double.parseDouble(pair[0].trim())
1154                    / Double.parseDouble(pair[1].trim());
1155
1156            pair = parts[1].split("/");
1157            double minutes = Double.parseDouble(pair[0].trim())
1158                    / Double.parseDouble(pair[1].trim());
1159
1160            pair = parts[2].split("/");
1161            double seconds = Double.parseDouble(pair[0].trim())
1162                    / Double.parseDouble(pair[1].trim());
1163
1164            double result = degrees + (minutes / 60.0) + (seconds / 3600.0);
1165            if ((ref.equals("S") || ref.equals("W"))) {
1166                return (float) -result;
1167            }
1168            return (float) result;
1169        } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
1170            // Not valid
1171            throw new IllegalArgumentException();
1172        }
1173    }
1174
1175    // Loads EXIF attributes from a JPEG input stream.
1176    private void getJpegAttributes(InputStream inputStream) throws IOException {
1177        // See JPEG File Interchange Format Specification page 5.
1178        if (DEBUG) {
1179            Log.d(TAG, "getJpegAttributes starting with: " + inputStream);
1180        }
1181        DataInputStream dataInputStream = new DataInputStream(inputStream);
1182        byte marker;
1183        int bytesRead = 0;
1184        if ((marker = dataInputStream.readByte()) != MARKER) {
1185            throw new IOException("Invalid marker: " + Integer.toHexString(marker & 0xff));
1186        }
1187        ++bytesRead;
1188        if (dataInputStream.readByte() != MARKER_SOI) {
1189            throw new IOException("Invalid marker: " + Integer.toHexString(marker & 0xff));
1190        }
1191        ++bytesRead;
1192        while (true) {
1193            marker = dataInputStream.readByte();
1194            if (marker != MARKER) {
1195                throw new IOException("Invalid marker:" + Integer.toHexString(marker & 0xff));
1196            }
1197            ++bytesRead;
1198            marker = dataInputStream.readByte();
1199            if (DEBUG) {
1200                Log.d(TAG, "Found JPEG segment indicator: " + Integer.toHexString(marker & 0xff));
1201            }
1202            ++bytesRead;
1203
1204            // EOI indicates the end of an image and in case of SOS, JPEG image stream starts and
1205            // the image data will terminate right after.
1206            if (marker == MARKER_EOI || marker == MARKER_SOS) {
1207                break;
1208            }
1209            int length = dataInputStream.readUnsignedShort() - 2;
1210            bytesRead += 2;
1211            if (DEBUG) {
1212                Log.d(TAG, "JPEG segment: " + Integer.toHexString(marker & 0xff) + " (length: "
1213                        + (length + 2) + ")");
1214            }
1215            if (length < 0) {
1216                throw new IOException("Invalid length");
1217            }
1218            switch (marker) {
1219                case MARKER_APP1: {
1220                    if (DEBUG) {
1221                        Log.d(TAG, "MARKER_APP1");
1222                    }
1223                    if (length < 6) {
1224                        // Skip if it's not an EXIF APP1 segment.
1225                        break;
1226                    }
1227                    byte[] identifier = new byte[6];
1228                    if (inputStream.read(identifier) != 6) {
1229                        throw new IOException("Invalid exif");
1230                    }
1231                    bytesRead += 6;
1232                    length -= 6;
1233                    if (!Arrays.equals(identifier, IDENTIFIER_EXIF_APP1)) {
1234                        // Skip if it's not an EXIF APP1 segment.
1235                        break;
1236                    }
1237                    if (length <= 0) {
1238                        throw new IOException("Invalid exif");
1239                    }
1240                    if (DEBUG) {
1241                        Log.d(TAG, "readExifSegment with a byte array (length: " + length + ")");
1242                    }
1243                    byte[] bytes = new byte[length];
1244                    if (dataInputStream.read(bytes) != length) {
1245                        throw new IOException("Invalid exif");
1246                    }
1247                    readExifSegment(bytes, bytesRead);
1248                    bytesRead += length;
1249                    length = 0;
1250                    break;
1251                }
1252
1253                case MARKER_COM: {
1254                    byte[] bytes = new byte[length];
1255                    if (dataInputStream.read(bytes) != length) {
1256                        throw new IOException("Invalid exif");
1257                    }
1258                    length = 0;
1259                    setAttribute("UserComment", new String(bytes, Charset.forName("US-ASCII")));
1260                    break;
1261                }
1262
1263                case MARKER_SOF0:
1264                case MARKER_SOF1:
1265                case MARKER_SOF2:
1266                case MARKER_SOF3:
1267                case MARKER_SOF5:
1268                case MARKER_SOF6:
1269                case MARKER_SOF7:
1270                case MARKER_SOF9:
1271                case MARKER_SOF10:
1272                case MARKER_SOF11:
1273                case MARKER_SOF13:
1274                case MARKER_SOF14:
1275                case MARKER_SOF15: {
1276                    if (dataInputStream.skipBytes(1) != 1) {
1277                        throw new IOException("Invalid SOFx");
1278                    }
1279                    setAttribute("ImageLength",
1280                            String.valueOf(dataInputStream.readUnsignedShort()));
1281                    setAttribute("ImageWidth", String.valueOf(dataInputStream.readUnsignedShort()));
1282                    length -= 5;
1283                    break;
1284                }
1285
1286                default: {
1287                    break;
1288                }
1289            }
1290            if (length < 0) {
1291                throw new IOException("Invalid length");
1292            }
1293            if (dataInputStream.skipBytes(length) != length) {
1294                throw new IOException("Invalid JPEG segment");
1295            }
1296            bytesRead += length;
1297        }
1298    }
1299
1300    // Stores a new JPEG image with EXIF attributes into a given output stream.
1301    private void saveJpegAttributes(InputStream inputStream, OutputStream outputStream)
1302            throws IOException {
1303        // See JPEG File Interchange Format Specification page 5.
1304        if (DEBUG) {
1305            Log.d(TAG, "saveJpegAttributes starting with (inputStream: " + inputStream
1306                    + ", outputStream: " + outputStream + ")");
1307        }
1308        DataInputStream dataInputStream = new DataInputStream(inputStream);
1309        ExifDataOutputStream dataOutputStream = new ExifDataOutputStream(outputStream);
1310        if (dataInputStream.readByte() != MARKER) {
1311            throw new IOException("Invalid marker");
1312        }
1313        dataOutputStream.writeByte(MARKER);
1314        if (dataInputStream.readByte() != MARKER_SOI) {
1315            throw new IOException("Invalid marker");
1316        }
1317        dataOutputStream.writeByte(MARKER_SOI);
1318
1319        // Write EXIF APP1 segment
1320        dataOutputStream.writeByte(MARKER);
1321        dataOutputStream.writeByte(MARKER_APP1);
1322        writeExifSegment(dataOutputStream, 6);
1323
1324        byte[] bytes = new byte[4096];
1325
1326        while (true) {
1327            byte marker = dataInputStream.readByte();
1328            if (marker != MARKER) {
1329                throw new IOException("Invalid marker");
1330            }
1331            marker = dataInputStream.readByte();
1332            switch (marker) {
1333                case MARKER_APP1: {
1334                    int length = dataInputStream.readUnsignedShort() - 2;
1335                    if (length < 0) {
1336                        throw new IOException("Invalid length");
1337                    }
1338                    byte[] identifier = new byte[6];
1339                    if (length >= 6) {
1340                        if (dataInputStream.read(identifier) != 6) {
1341                            throw new IOException("Invalid exif");
1342                        }
1343                        if (Arrays.equals(identifier, IDENTIFIER_EXIF_APP1)) {
1344                            // Skip the original EXIF APP1 segment.
1345                            if (dataInputStream.skip(length - 6) != length - 6) {
1346                                throw new IOException("Invalid length");
1347                            }
1348                            break;
1349                        }
1350                    }
1351                    // Copy non-EXIF APP1 segment.
1352                    dataOutputStream.writeByte(MARKER);
1353                    dataOutputStream.writeByte(marker);
1354                    dataOutputStream.writeUnsignedShort(length + 2);
1355                    if (length >= 6) {
1356                        length -= 6;
1357                        dataOutputStream.write(identifier);
1358                    }
1359                    int read;
1360                    while (length > 0 && (read = dataInputStream.read(
1361                            bytes, 0, Math.min(length, bytes.length))) >= 0) {
1362                        dataOutputStream.write(bytes, 0, read);
1363                        length -= read;
1364                    }
1365                    break;
1366                }
1367                case MARKER_EOI:
1368                case MARKER_SOS: {
1369                    dataOutputStream.writeByte(MARKER);
1370                    dataOutputStream.writeByte(marker);
1371                    // Copy all the remaining data
1372                    Streams.copy(dataInputStream, dataOutputStream);
1373                    return;
1374                }
1375                default: {
1376                    // Copy JPEG segment
1377                    dataOutputStream.writeByte(MARKER);
1378                    dataOutputStream.writeByte(marker);
1379                    int length = dataInputStream.readUnsignedShort();
1380                    dataOutputStream.writeUnsignedShort(length);
1381                    length -= 2;
1382                    if (length < 0) {
1383                        throw new IOException("Invalid length");
1384                    }
1385                    int read;
1386                    while (length > 0 && (read = dataInputStream.read(
1387                            bytes, 0, Math.min(length, bytes.length))) >= 0) {
1388                        dataOutputStream.write(bytes, 0, read);
1389                        length -= read;
1390                    }
1391                    break;
1392                }
1393            }
1394        }
1395    }
1396
1397    // Reads the given EXIF byte area and save its tag data into attributes.
1398    private void readExifSegment(byte[] exifBytes, int exifOffsetFromBeginning) throws IOException {
1399        // Parse TIFF Headers. See JEITA CP-3451C Table 1. page 10.
1400        ByteOrderAwarenessDataInputStream dataInputStream =
1401                new ByteOrderAwarenessDataInputStream(exifBytes);
1402
1403        // Read byte align
1404        short byteOrder = dataInputStream.readShort();
1405        switch (byteOrder) {
1406            case BYTE_ALIGN_II:
1407                if (DEBUG) {
1408                    Log.d(TAG, "readExifSegment: Byte Align II");
1409                }
1410                dataInputStream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
1411                break;
1412            case BYTE_ALIGN_MM:
1413                if (DEBUG) {
1414                    Log.d(TAG, "readExifSegment: Byte Align MM");
1415                }
1416                dataInputStream.setByteOrder(ByteOrder.BIG_ENDIAN);
1417                break;
1418            default:
1419                throw new IOException("Invalid byte order: " + Integer.toHexString(byteOrder));
1420        }
1421
1422        int startCode = dataInputStream.readUnsignedShort();
1423        if (startCode != 0x2a) {
1424            throw new IOException("Invalid exif start: " + Integer.toHexString(startCode));
1425        }
1426
1427        // Read first ifd offset
1428        long firstIfdOffset = dataInputStream.readUnsignedInt();
1429        if (firstIfdOffset < 8 || firstIfdOffset >= exifBytes.length) {
1430            throw new IOException("Invalid first Ifd offset: " + firstIfdOffset);
1431        }
1432        firstIfdOffset -= 8;
1433        if (firstIfdOffset > 0) {
1434            if (dataInputStream.skip(firstIfdOffset) != firstIfdOffset) {
1435                throw new IOException("Couldn't jump to first Ifd: " + firstIfdOffset);
1436            }
1437        }
1438
1439        // Read primary image TIFF image file directory.
1440        readImageFileDirectory(dataInputStream, IFD_TIFF_HINT);
1441
1442        // Process thumbnail.
1443        String jpegInterchangeFormatString = getAttribute(JPEG_INTERCHANGE_FORMAT_TAG.name);
1444        String jpegInterchangeFormatLengthString =
1445                getAttribute(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name);
1446        if (jpegInterchangeFormatString != null && jpegInterchangeFormatLengthString != null) {
1447            try {
1448                int jpegInterchangeFormat = Integer.parseInt(jpegInterchangeFormatString);
1449                int jpegInterchangeFormatLength = Integer
1450                        .parseInt(jpegInterchangeFormatLengthString);
1451                // The following code limits the size of thumbnail size not to overflow EXIF data area.
1452                jpegInterchangeFormatLength = Math.min(jpegInterchangeFormat
1453                        + jpegInterchangeFormatLength, exifOffsetFromBeginning + exifBytes.length)
1454                        - jpegInterchangeFormat;
1455                if (jpegInterchangeFormat > 0 && jpegInterchangeFormatLength > 0) {
1456                    mHasThumbnail = true;
1457                    mThumbnailOffset = exifOffsetFromBeginning + jpegInterchangeFormat;
1458                    mThumbnailLength = jpegInterchangeFormatLength;
1459
1460                    if (mFilename == null && mAssetInputStream == null
1461                            && mSeekableFileDescriptor == null) {
1462                        // Save the thumbnail in memory if the input doesn't support reading again.
1463                        byte[] thumbnailBytes = new byte[jpegInterchangeFormatLength];
1464                        dataInputStream.seek(jpegInterchangeFormat);
1465                        dataInputStream.readFully(thumbnailBytes);
1466                        mThumbnailBytes = thumbnailBytes;
1467
1468                        if (DEBUG) {
1469                            Bitmap bitmap = BitmapFactory.decodeByteArray(
1470                                    thumbnailBytes, 0, thumbnailBytes.length);
1471                            Log.d(TAG, "Thumbnail offset: " + mThumbnailOffset + ", length: "
1472                                    + mThumbnailLength + ", width: " + bitmap.getWidth()
1473                                    + ", height: "
1474                                    + bitmap.getHeight());
1475                        }
1476                    }
1477                }
1478            } catch (NumberFormatException e) {
1479                // Ignored the corrupted image.
1480            }
1481        }
1482
1483        // For compatibility, keep data formats as follows.
1484        convertToInt(TAG_IMAGE_WIDTH);
1485        convertToInt(TAG_IMAGE_LENGTH);
1486        convertToInt(TAG_ORIENTATION);
1487        convertToInt(TAG_FLASH);
1488        convertToRational(TAG_FOCAL_LENGTH);
1489        convertToDouble(TAG_DIGITAL_ZOOM_RATIO);
1490        convertToDouble(TAG_EXPOSURE_TIME);
1491        convertToDouble(TAG_F_NUMBER);
1492        convertToDouble(TAG_SUBJECT_DISTANCE);
1493        convertToInt(TAG_ISO);
1494        convertToDouble(TAG_EXPOSURE_BIAS_VALUE);
1495        convertToInt(TAG_WHITE_BALANCE);
1496        convertToInt(TAG_LIGHT_SOURCE);
1497        convertToInt(TAG_METERING_MODE);
1498        convertToInt(TAG_EXPOSURE_PROGRAM);
1499        convertToInt(TAG_EXPOSURE_MODE);
1500        convertToRational(TAG_GPS_ALTITUDE);
1501        convertToInt(TAG_GPS_ALTITUDE_REF);
1502        convertToRational(TAG_GPS_LONGITUDE);
1503        convertToRational(TAG_GPS_LATITUDE);
1504        convertToTimetamp(TAG_GPS_TIMESTAMP);
1505
1506        // The value of DATETIME tag has the same value of DATETIME_ORIGINAL tag.
1507        String valueOfDateTimeOriginal = getAttribute("DateTimeOriginal");
1508        if (valueOfDateTimeOriginal != null) {
1509            setAttribute(TAG_DATETIME, valueOfDateTimeOriginal);
1510        }
1511
1512        // Add the default value.
1513        if (getAttribute(TAG_IMAGE_WIDTH) == null) {
1514            setAttribute(TAG_IMAGE_WIDTH, "0");
1515        }
1516        if (getAttribute(TAG_IMAGE_LENGTH) == null) {
1517            setAttribute(TAG_IMAGE_LENGTH, "0");
1518        }
1519        if (getAttribute(TAG_ORIENTATION) == null) {
1520            setAttribute(TAG_ORIENTATION, "0");
1521        }
1522        if (getAttribute(TAG_LIGHT_SOURCE) == null) {
1523            setAttribute(TAG_LIGHT_SOURCE, "0");
1524        }
1525    }
1526
1527    // Converts the tag value to timestamp; Otherwise deletes the given tag.
1528    private void convertToTimetamp(String tagName) {
1529        String entryValue = getAttribute(tagName);
1530        if (entryValue == null) return;
1531        int dataFormat = getDataFormatOfExifEntryValue(entryValue);
1532        String[] components = entryValue.split(",");
1533        if (dataFormat == IFD_FORMAT_SRATIONAL && components.length == 3) {
1534            StringBuilder stringBuilder = new StringBuilder();
1535            for (String component : components) {
1536                if (stringBuilder.length() > 0) {
1537                    stringBuilder.append(":");
1538                }
1539                String[] rationalNumber = component.split("/");
1540                int numerator = Integer.parseInt(rationalNumber[0]);
1541                int denominator = Integer.parseInt(rationalNumber[1]);
1542                if (denominator == 0) {
1543                    numerator = 0;
1544                    denominator = 1;
1545                }
1546                int value = numerator / denominator;
1547                stringBuilder.append(String.format("%02d", value));
1548            }
1549            setAttribute(tagName, stringBuilder.toString());
1550        } else if (dataFormat != IFD_FORMAT_STRING) {
1551            setAttribute(tagName, null);
1552        }
1553    }
1554
1555    // Checks the tag value of a given tag formatted in double type; Otherwise try to convert it to
1556    // double type or delete it.
1557    private void convertToDouble(String tagName) {
1558        String entryValue = getAttribute(tagName);
1559        if (entryValue == null) return;
1560        int dataFormat = getDataFormatOfExifEntryValue(entryValue);
1561        switch (dataFormat) {
1562            case IFD_FORMAT_SRATIONAL: {
1563                StringBuilder stringBuilder = new StringBuilder();
1564                String[] components = entryValue.split(",");
1565                for (String component : components) {
1566                    if (stringBuilder.length() > 0) {
1567                        stringBuilder.append(",");
1568                    }
1569                    String[] rationalNumber = component.split("/");
1570                    int numerator = Integer.parseInt(rationalNumber[0]);
1571                    int denominator = Integer.parseInt(rationalNumber[1]);
1572                    if (denominator == 0) {
1573                        numerator = 0;
1574                        denominator = 1;
1575                    }
1576                    stringBuilder.append((double) numerator / denominator);
1577                }
1578                setAttribute(tagName, stringBuilder.toString());
1579                break;
1580            }
1581            case IFD_FORMAT_DOUBLE:
1582                // Keep it as is.
1583                break;
1584            default:
1585                setAttribute(tagName, null);
1586                break;
1587        }
1588    }
1589
1590    // Checks the tag value of a given tag formatted in int type; Otherwise deletes the tag value.
1591    private void convertToRational(String tagName) {
1592        String entryValue = getAttribute(tagName);
1593        if (entryValue == null) return;
1594        int dataFormat = getDataFormatOfExifEntryValue(entryValue);
1595        switch (dataFormat) {
1596            case IFD_FORMAT_SLONG:
1597            case IFD_FORMAT_DOUBLE: {
1598                StringBuilder stringBuilder = new StringBuilder();
1599                String[] components = entryValue.split(",");
1600                for (String component : components) {
1601                    if (stringBuilder.length() > 0) {
1602                        stringBuilder.append(",");
1603                    }
1604                    double doubleValue = Double.parseDouble(component);
1605                    stringBuilder.append((int) (doubleValue * 10000.0)).append("/").append(10000);
1606                }
1607                setAttribute(tagName, stringBuilder.toString());
1608                break;
1609            }
1610            case IFD_FORMAT_SRATIONAL:
1611                // Keep it as is.
1612                break;
1613            default:
1614                setAttribute(tagName, null);
1615                break;
1616        }
1617    }
1618
1619    // Checks the tag value of a given tag formatted in int type; Otherwise deletes the tag value.
1620    private void convertToInt(String tagName) {
1621        String entryValue = getAttribute(tagName);
1622        if (entryValue == null) return;
1623        int dataFormat = getDataFormatOfExifEntryValue(entryValue);
1624        if (dataFormat != IFD_FORMAT_SLONG) {
1625            setAttribute(tagName, null);
1626        }
1627    }
1628
1629    // Reads image file directory, which is a tag group in EXIF.
1630    private void readImageFileDirectory(ByteOrderAwarenessDataInputStream dataInputStream, int hint)
1631            throws IOException {
1632        if (dataInputStream.peek() + 2 > dataInputStream.mLength) {
1633            // Return if there is no data from the offset.
1634            return;
1635        }
1636        // See JEITA CP-3451 Figure 5. page 9.
1637        short numberOfDirectoryEntry = dataInputStream.readShort();
1638        if (dataInputStream.peek() + 12 * numberOfDirectoryEntry > dataInputStream.mLength) {
1639            // Return if the size of entries is too big.
1640            return;
1641        }
1642
1643        if (DEBUG) {
1644            Log.d(TAG, "numberOfDirectoryEntry: " + numberOfDirectoryEntry);
1645        }
1646
1647        for (short i = 0; i < numberOfDirectoryEntry; ++i) {
1648            int tagNumber = dataInputStream.readUnsignedShort();
1649            int dataFormat = dataInputStream.readUnsignedShort();
1650            int numberOfComponents = dataInputStream.readInt();
1651            long nextEntryOffset = dataInputStream.peek() + 4;  // next four bytes is for data
1652                                                                // offset or value.
1653            // Look up a corresponding tag from tag number
1654            String tagName = (String) sExifTagMapsForReading[hint].get(tagNumber);
1655
1656            if (DEBUG) {
1657                Log.d(TAG, String.format("hint: %d, tagNumber: %d, tagName: %s, dataFormat: %d, " +
1658                        "numberOfComponents: %d", hint, tagNumber, tagName, dataFormat,
1659                        numberOfComponents));
1660            }
1661
1662            if (tagName == null || dataFormat <= 0 ||
1663                    dataFormat >= IFD_FORMAT_BYTES_PER_FORMAT.length) {
1664                // Skip if the parsed tag number is not defined or invalid data format.
1665                if (tagName == null) {
1666                    Log.w(TAG, "Skip the tag entry since tag number is not defined: " + tagNumber);
1667                } else {
1668                    Log.w(TAG, "Skip the tag entry since data format is invalid: " + dataFormat);
1669                }
1670                dataInputStream.seek(nextEntryOffset);
1671                continue;
1672            }
1673
1674            // Read a value from data field or seek to the value offset which is stored in data
1675            // field if the size of the entry value is bigger than 4.
1676            int byteCount = numberOfComponents * IFD_FORMAT_BYTES_PER_FORMAT[dataFormat];
1677            if (byteCount > 4) {
1678                long offset = dataInputStream.readUnsignedInt();
1679                if (DEBUG) {
1680                    Log.d(TAG, "seek to data offset: " + offset);
1681                }
1682                if (offset + byteCount <= dataInputStream.mLength) {
1683                    dataInputStream.seek(offset);
1684                } else {
1685                     // Skip if invalid data offset.
1686                    Log.w(TAG, "Skip the tag entry since data offset is invalid: " + offset);
1687                    dataInputStream.seek(nextEntryOffset);
1688                    continue;
1689                }
1690            }
1691
1692            // Recursively parse IFD when a IFD pointer tag appears.
1693            int innerIfdHint = getIfdHintFromTagNumber(tagNumber);
1694            if (DEBUG) {
1695                Log.d(TAG, "innerIfdHint: " + innerIfdHint + " byteCount: " + byteCount);
1696            }
1697            if (innerIfdHint >= 0) {
1698                long offset = -1L;
1699                // Get offset from data field
1700                switch (dataFormat) {
1701                    case IFD_FORMAT_USHORT: {
1702                        offset = dataInputStream.readUnsignedShort();
1703                        break;
1704                    }
1705                    case IFD_FORMAT_SSHORT: {
1706                        offset = dataInputStream.readShort();
1707                        break;
1708                    }
1709                    case IFD_FORMAT_ULONG: {
1710                        offset = dataInputStream.readUnsignedInt();
1711                        break;
1712                    }
1713                    case IFD_FORMAT_SLONG: {
1714                        offset = dataInputStream.readInt();
1715                        break;
1716                    }
1717                    default: {
1718                        // Nothing to do
1719                        break;
1720                    }
1721                }
1722                if (DEBUG) {
1723                    Log.d(TAG, String.format("Offset: %d, tagName: %s", offset, tagName));
1724                }
1725                if (offset > 0L && offset < dataInputStream.mLength) {
1726                    dataInputStream.seek(offset);
1727                    readImageFileDirectory(dataInputStream, innerIfdHint);
1728                } else {
1729                    Log.w(TAG, "Skip jump into the IFD since its offset is invalid: " + offset);
1730                }
1731
1732                dataInputStream.seek(nextEntryOffset);
1733                continue;
1734            }
1735
1736            if (numberOfComponents == 1 || dataFormat == IFD_FORMAT_STRING
1737                    || dataFormat == IFD_FORMAT_UNDEFINED) {
1738                String entryValue = readExifEntryValue(
1739                        dataInputStream, dataFormat, numberOfComponents);
1740                if (entryValue != null) {
1741                    setAttribute(tagName, entryValue);
1742                }
1743            } else {
1744                StringBuilder entryValueBuilder = new StringBuilder();
1745                for (int c = 0; c < numberOfComponents; ++c) {
1746                    if (entryValueBuilder.length() > 0) {
1747                        entryValueBuilder.append(",");
1748                    }
1749                    entryValueBuilder.append(readExifEntryValue(
1750                            dataInputStream, dataFormat, numberOfComponents));
1751                }
1752                setAttribute(tagName, entryValueBuilder.toString());
1753            }
1754
1755            if (dataInputStream.peek() != nextEntryOffset) {
1756                dataInputStream.seek(nextEntryOffset);
1757            }
1758        }
1759
1760        if (dataInputStream.peek() + 4 <= dataInputStream.mLength) {
1761            long nextIfdOffset = dataInputStream.readUnsignedInt();
1762            if (DEBUG) {
1763                Log.d(TAG, String.format("nextIfdOffset: %d", nextIfdOffset));
1764            }
1765            // The next IFD offset needs to be bigger than 8
1766            // since the first IFD offset is at least 8.
1767            if (nextIfdOffset > 8 && nextIfdOffset < dataInputStream.mLength) {
1768                dataInputStream.seek(nextIfdOffset);
1769                readImageFileDirectory(dataInputStream, IFD_THUMBNAIL_HINT);
1770            }
1771        }
1772    }
1773
1774    // Reads a value from where the entry value are stored.
1775    private String readExifEntryValue(ByteOrderAwarenessDataInputStream dataInputStream,
1776            int dataFormat, int numberOfComponents) throws IOException {
1777        // See TIFF 6.0 spec Types. page 15.
1778        switch (dataFormat) {
1779            case IFD_FORMAT_BYTE: {
1780                return String.valueOf(dataInputStream.readByte());
1781            }
1782            case IFD_FORMAT_SBYTE: {
1783                return String.valueOf(dataInputStream.readByte() & 0xff);
1784            }
1785            case IFD_FORMAT_USHORT: {
1786                return String.valueOf(dataInputStream.readUnsignedShort());
1787            }
1788            case IFD_FORMAT_SSHORT: {
1789                return String.valueOf(dataInputStream.readUnsignedInt());
1790            }
1791            case IFD_FORMAT_ULONG: {
1792                return String.valueOf(dataInputStream.readInt());
1793            }
1794            case IFD_FORMAT_SLONG: {
1795                return String.valueOf(dataInputStream.readInt());
1796            }
1797            case IFD_FORMAT_URATIONAL:
1798            case IFD_FORMAT_SRATIONAL: {
1799                int numerator = dataInputStream.readInt();
1800                int denominator = dataInputStream.readInt();
1801                return numerator + "/" + denominator;
1802            }
1803            case IFD_FORMAT_SINGLE: {
1804                return String.valueOf(dataInputStream.readFloat());
1805            }
1806            case IFD_FORMAT_DOUBLE: {
1807                return String.valueOf(dataInputStream.readDouble());
1808            }
1809            case IFD_FORMAT_UNDEFINED:  // Usually UNDEFINED format is ASCII.
1810            case IFD_FORMAT_STRING: {
1811                byte[] bytes = new byte[numberOfComponents];
1812                dataInputStream.readFully(bytes);
1813                int index = 0;
1814                if (numberOfComponents >= EXIF_ASCII_PREFIX.length) {
1815                    boolean same = true;
1816                    for (int i = 0; i < EXIF_ASCII_PREFIX.length; ++i) {
1817                        if (bytes[i] != EXIF_ASCII_PREFIX[i]) {
1818                            same = false;
1819                            break;
1820                        }
1821                    }
1822                    if (same) {
1823                        index = EXIF_ASCII_PREFIX.length;
1824                    }
1825                }
1826
1827                StringBuilder stringBuilder = new StringBuilder();
1828                while (index < numberOfComponents) {
1829                    int ch = bytes[index];
1830                    if (ch == 0) {
1831                        break;
1832                    }
1833                    if (ch >= 32) {
1834                        stringBuilder.append((char) ch);
1835                    }
1836                    else {
1837                        stringBuilder.append('?');
1838                    }
1839                    ++index;
1840                }
1841                return stringBuilder.toString();
1842            }
1843            default: {
1844                // Nothing to do
1845                return null;
1846            }
1847        }
1848    }
1849
1850    // Gets the corresponding IFD group index of the given tag number for writing Exif Tags.
1851    private static int getIfdHintFromTagNumber(int tagNumber) {
1852        for (int i = 0; i < IFD_POINTER_TAG_HINTS.length; ++i) {
1853            if (IFD_POINTER_TAGS[i].number == tagNumber) {
1854                return IFD_POINTER_TAG_HINTS[i];
1855            }
1856        }
1857        return -1;
1858    }
1859
1860    // Writes an Exif segment into the given output stream.
1861    private int writeExifSegment(ExifDataOutputStream dataOutputStream, int exifOffsetFromBeginning)
1862            throws IOException {
1863        // The following variables are for calculating each IFD tag group size in bytes.
1864        int[] ifdOffsets = new int[EXIF_TAGS.length];
1865        int[] ifdDataSizes = new int[EXIF_TAGS.length];
1866
1867        // Remove IFD pointer tags (we'll re-add it later.)
1868        for (ExifTag tag : IFD_POINTER_TAGS) {
1869            setAttribute(tag.name, null);
1870        }
1871
1872        // Add IFD pointer tags. The next offset of primary image TIFF IFD will have thumbnail IFD
1873        // offset when there is one or more tags in the thumbnail IFD.
1874        if (!mAttributes[IFD_INTEROPERABILITY_HINT].isEmpty()) {
1875            mAttributes[IFD_EXIF_HINT].put(IFD_POINTER_TAGS[2].name, "0");
1876        }
1877        if (!mAttributes[IFD_EXIF_HINT].isEmpty()) {
1878            mAttributes[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[0].name, "0");
1879        }
1880        if (!mAttributes[IFD_GPS_HINT].isEmpty()) {
1881            mAttributes[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[1].name, "0");
1882        }
1883        // Remove old thumbnail data
1884        setAttribute(JPEG_INTERCHANGE_FORMAT_TAG.name, null);
1885        setAttribute(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name, null);
1886        if (mHasThumbnail) {
1887            mAttributes[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_TAG.name, "0");
1888            mAttributes[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name,
1889                    String.valueOf(mThumbnailLength));
1890        }
1891
1892        // Remove null value tags.
1893        for (int hint = 0; hint < EXIF_TAGS.length; ++hint) {
1894            for (Object obj : mAttributes[hint].entrySet().toArray()) {
1895                Map.Entry entry = (Map.Entry) obj;
1896                if (entry.getValue() == null) {
1897                    mAttributes[hint].remove(entry.getKey());
1898                }
1899            }
1900        }
1901
1902        // Calculate IFD group data area sizes. IFD group data area is assigned to save the entry
1903        // value which has a bigger size than 4 bytes.
1904        for (int i = 0; i < 5; ++i) {
1905            int sum = 0;
1906            for (Map.Entry entry : (Set<Map.Entry>) mAttributes[i].entrySet()) {
1907                String entryValue = (String) ((Map.Entry) entry).getValue();
1908                int dataFormat = getDataFormatOfExifEntryValue(entryValue);
1909                int size = getSizeOfExifEntryValue(dataFormat, entryValue);
1910                if (size > 4) {
1911                    sum += size;
1912                }
1913            }
1914            ifdDataSizes[i] += sum;
1915        }
1916
1917        // Calculate IFD offsets.
1918        int position = 8;
1919        for (int hint = 0; hint < EXIF_TAGS.length; ++hint) {
1920            if (!mAttributes[hint].isEmpty()) {
1921                ifdOffsets[hint] = position;
1922                position += 2 + mAttributes[hint].size() * 12 + 4 + ifdDataSizes[hint];
1923            }
1924        }
1925        if (mHasThumbnail) {
1926            int thumbnailOffset = position;
1927            mAttributes[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_TAG.name,
1928                    String.valueOf(thumbnailOffset));
1929            mAttributes[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name,
1930                    String.valueOf(mThumbnailLength));
1931            mThumbnailOffset = exifOffsetFromBeginning + thumbnailOffset;
1932            position += mThumbnailLength;
1933        }
1934
1935        // Calculate the total size
1936        int totalSize = position + 8;  // eight bytes is for header part.
1937        if (DEBUG) {
1938            Log.d(TAG, "totalSize length: " + totalSize);
1939            for (int i = 0; i < 5; ++i) {
1940                Log.d(TAG, String.format("index: %d, offsets: %d, tag count: %d, data sizes: %d",
1941                        i, ifdOffsets[i], mAttributes[i].size(), ifdDataSizes[i]));
1942            }
1943        }
1944
1945        // Update IFD pointer tags with the calculated offsets.
1946        if (!mAttributes[IFD_EXIF_HINT].isEmpty()) {
1947            mAttributes[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[0].name,
1948                    String.valueOf(ifdOffsets[IFD_EXIF_HINT]));
1949        }
1950        if (!mAttributes[IFD_GPS_HINT].isEmpty()) {
1951            mAttributes[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[1].name,
1952                    String.valueOf(ifdOffsets[IFD_GPS_HINT]));
1953        }
1954        if (!mAttributes[IFD_INTEROPERABILITY_HINT].isEmpty()) {
1955            mAttributes[IFD_EXIF_HINT].put(IFD_POINTER_TAGS[2].name,
1956                    String.valueOf(ifdOffsets[IFD_INTEROPERABILITY_HINT]));
1957        }
1958
1959        // Write TIFF Headers. See JEITA CP-3451C Table 1. page 10.
1960        dataOutputStream.writeUnsignedShort(totalSize);
1961        dataOutputStream.write(IDENTIFIER_EXIF_APP1);
1962        dataOutputStream.writeShort(BYTE_ALIGN_MM);
1963        dataOutputStream.writeUnsignedShort(0x2a);
1964        dataOutputStream.writeUnsignedInt(8);
1965
1966        // Write IFD groups. See JEITA CP-3451C Figure 7. page 12.
1967        for (int hint = 0; hint < EXIF_TAGS.length; ++hint) {
1968            if (!mAttributes[hint].isEmpty()) {
1969                // See JEITA CP-3451C 4.6.2 IFD structure. page 13.
1970                // Write entry count
1971                dataOutputStream.writeUnsignedShort(mAttributes[hint].size());
1972
1973                // Write entry info
1974                int dataOffset = ifdOffsets[hint] + 2 + mAttributes[hint].size() * 12 + 4;
1975                for (Map.Entry entry : (Set<Map.Entry>) mAttributes[hint].entrySet()) {
1976                    // Convert tag name to tag number.
1977                    int tagNumber = (int) sExifTagMapsForWriting[hint].get(entry.getKey());
1978                    String entryValue = (String) entry.getValue();
1979
1980                    int dataFormat = getDataFormatOfExifEntryValue(entryValue);
1981                    int numberOfComponents = getNumberOfComponentsInExifEntryValue(dataFormat,
1982                            entryValue);
1983                    int byteCount = getSizeOfExifEntryValue(dataFormat, entryValue);
1984
1985                    dataOutputStream.writeUnsignedShort(tagNumber);
1986                    dataOutputStream.writeUnsignedShort(dataFormat);
1987                    dataOutputStream.writeInt(numberOfComponents);
1988                    if (byteCount > 4) {
1989                        dataOutputStream.writeUnsignedInt(dataOffset);
1990                        dataOffset += byteCount;
1991                    } else {
1992                        int bytesWritten = writeExifEntryValue(dataOutputStream, entryValue);
1993                        // Fill zero up to 4 bytes
1994                        if (bytesWritten < 4) {
1995                            for (int i = bytesWritten; i < 4; ++i) {
1996                                dataOutputStream.write(0);
1997                            }
1998                        }
1999                    }
2000                }
2001
2002                // Write the next offset. It writes the offset of thumbnail IFD if there is one or
2003                // more tags in the thumbnail IFD when the current IFD is the primary image TIFF
2004                // IFD; Otherwise 0.
2005                if (hint == 0 && !mAttributes[IFD_THUMBNAIL_HINT].isEmpty()) {
2006                    dataOutputStream.writeUnsignedInt(ifdOffsets[IFD_THUMBNAIL_HINT]);
2007                } else {
2008                    dataOutputStream.writeUnsignedInt(0);
2009                }
2010
2011                // Write values of data field exceeding 4 bytes after the next offset.
2012                for (Map.Entry entry : (Set<Map.Entry>) mAttributes[hint].entrySet()) {
2013                    String entryValue = (String) entry.getValue();
2014
2015                    int dataFormat = getDataFormatOfExifEntryValue(entryValue);
2016                    int byteCount = getSizeOfExifEntryValue(dataFormat, entryValue);
2017                    if (byteCount > 4) {
2018                        writeExifEntryValue(dataOutputStream, entryValue);
2019                    }
2020                }
2021            }
2022        }
2023
2024        // Write thumbnail
2025        if (mHasThumbnail) {
2026            dataOutputStream.write(getThumbnail());
2027        }
2028
2029        return totalSize;
2030    }
2031
2032    // Writes EXIF entry value and its entry value type will be automatically determined.
2033    private static int writeExifEntryValue(ExifDataOutputStream dataOutputStream, String entryValue)
2034            throws IOException {
2035        int bytesWritten = 0;
2036        int dataFormat = getDataFormatOfExifEntryValue(entryValue);
2037
2038        if (dataFormat == IFD_FORMAT_STRING) {
2039            byte[] asciiArray = (entryValue + '\0').getBytes(Charset.forName("US-ASCII"));
2040            dataOutputStream.write(asciiArray);
2041            return asciiArray.length;
2042        }
2043
2044        // Values can be composed of several components. Each component is separated by char ','.
2045        String[] components = entryValue.split(",");
2046        for (String component : components) {
2047            switch (dataFormat) {
2048                case IFD_FORMAT_SLONG:
2049                    dataOutputStream.writeInt(Integer.parseInt(component));
2050                    bytesWritten += 4;
2051                    break;
2052                case IFD_FORMAT_DOUBLE:
2053                    dataOutputStream.writeDouble(Double.parseDouble(component));
2054                    bytesWritten += 8;
2055                    break;
2056                case IFD_FORMAT_SRATIONAL:
2057                    String[] rationalNumber = component.split("/");
2058                    dataOutputStream.writeInt(Integer.parseInt(rationalNumber[0]));
2059                    dataOutputStream.writeInt(Integer.parseInt(rationalNumber[1]));
2060                    bytesWritten += 8;
2061                    break;
2062                default:
2063                    throw new IllegalArgumentException();
2064            }
2065        }
2066        return bytesWritten;
2067    }
2068
2069    // Determines the data format of EXIF entry value.
2070    private static int getDataFormatOfExifEntryValue(String entryValue) {
2071        // See TIFF 6.0 spec Types. page 15.
2072        // Take the first component if there are more than one component.
2073        if (entryValue.contains(",")) {
2074            String[] entryValues = entryValue.split(",");
2075            int dataFormat = getDataFormatOfExifEntryValue(entryValues[0]);
2076            if (dataFormat == IFD_FORMAT_STRING) {
2077                return IFD_FORMAT_STRING;
2078            }
2079            for (int i = 1; i < entryValues.length; ++i) {
2080                if (getDataFormatOfExifEntryValue(entryValues[i]) != dataFormat) {
2081                    return IFD_FORMAT_STRING;
2082                }
2083            }
2084            return dataFormat;
2085        }
2086
2087        if (entryValue.contains("/")) {
2088            String[] rationalNumber = entryValue.split("/");
2089            if (rationalNumber.length == 2) {
2090                try {
2091                    Integer.parseInt(rationalNumber[0]);
2092                    Integer.parseInt(rationalNumber[1]);
2093                    return IFD_FORMAT_SRATIONAL;
2094                } catch (NumberFormatException e)  {
2095                    // Ignored
2096                }
2097            }
2098            return IFD_FORMAT_STRING;
2099        }
2100        try {
2101            Integer.parseInt(entryValue);
2102            return IFD_FORMAT_SLONG;
2103        } catch (NumberFormatException e) {
2104            // Ignored
2105        }
2106        try {
2107            Double.parseDouble(entryValue);
2108            return IFD_FORMAT_DOUBLE;
2109        } catch (NumberFormatException e) {
2110            // Ignored
2111        }
2112        return IFD_FORMAT_STRING;
2113    }
2114
2115    // Determines the size of EXIF entry value.
2116    private static int getSizeOfExifEntryValue(int dataFormat, String entryValue) {
2117        // See TIFF 6.0 spec Types page 15.
2118        if (dataFormat == IFD_FORMAT_STRING) {
2119            return (entryValue + '\0').getBytes(Charset.forName("US-ASCII")).length;
2120        }
2121        int bytesEstimated = 0;
2122        String[] components = entryValue.split(",");
2123        for (String component : components) {
2124            switch (dataFormat) {
2125                case IFD_FORMAT_SLONG:
2126                    bytesEstimated += 4;
2127                    break;
2128                case IFD_FORMAT_DOUBLE:
2129                    bytesEstimated += 8;
2130                    break;
2131                case IFD_FORMAT_SRATIONAL:
2132                    bytesEstimated += 8;
2133                    break;
2134                default:
2135                    throw new IllegalArgumentException();
2136            }
2137        }
2138        return bytesEstimated;
2139    }
2140
2141    // Determines the number of components of EXIF entry value.
2142    private static int getNumberOfComponentsInExifEntryValue(int dataFormat, String entryValue) {
2143        if (dataFormat == IFD_FORMAT_STRING) {
2144            return (entryValue + '\0').getBytes(Charset.forName("US-ASCII")).length;
2145        }
2146        int count = 1;
2147        for (int i = 0; i < entryValue.length(); ++i) {
2148            if (entryValue.charAt(i) == ',') {
2149                ++count;
2150            }
2151        }
2152        return count;
2153    }
2154
2155    // An input stream to parse EXIF data area, which can be written in either little or big endian
2156    // order.
2157    private static class ByteOrderAwarenessDataInputStream extends ByteArrayInputStream {
2158        private static final ByteOrder LITTLE_ENDIAN = ByteOrder.LITTLE_ENDIAN;
2159        private static final ByteOrder BIG_ENDIAN = ByteOrder.BIG_ENDIAN;
2160
2161        private ByteOrder mByteOrder = ByteOrder.BIG_ENDIAN;
2162        private final long mLength;
2163        private long mPosition;
2164
2165        public ByteOrderAwarenessDataInputStream(byte[] bytes) {
2166            super(bytes);
2167            mLength = bytes.length;
2168            mPosition = 0L;
2169        }
2170
2171        public void setByteOrder(ByteOrder byteOrder) {
2172            mByteOrder = byteOrder;
2173        }
2174
2175        public void seek(long byteCount) throws IOException {
2176            mPosition = 0L;
2177            reset();
2178            if (skip(byteCount) != byteCount) {
2179                throw new IOException("Couldn't seek up to the byteCount");
2180            }
2181        }
2182
2183        public long peek() {
2184            return mPosition;
2185        }
2186
2187        public void readFully(byte[] buffer) throws IOException {
2188            mPosition += buffer.length;
2189            if (mPosition > mLength) {
2190                throw new EOFException();
2191            }
2192            if (super.read(buffer, 0, buffer.length) != buffer.length) {
2193                throw new IOException("Couldn't read up to the length of buffer");
2194            }
2195        }
2196
2197        public byte readByte() throws IOException {
2198            ++mPosition;
2199            if (mPosition > mLength) {
2200                throw new EOFException();
2201            }
2202            int ch = super.read();
2203            if (ch < 0) {
2204                throw new EOFException();
2205            }
2206            return (byte) ch;
2207        }
2208
2209        public short readShort() throws IOException {
2210            mPosition += 2;
2211            if (mPosition > mLength) {
2212                throw new EOFException();
2213            }
2214            int ch1 = super.read();
2215            int ch2 = super.read();
2216            if ((ch1 | ch2) < 0) {
2217                throw new EOFException();
2218            }
2219            if (mByteOrder == LITTLE_ENDIAN) {
2220                return (short) ((ch2 << 8) + (ch1));
2221            } else if (mByteOrder == BIG_ENDIAN) {
2222                return (short) ((ch1 << 8) + (ch2));
2223            }
2224            throw new IOException("Invalid byte order: " + mByteOrder);
2225        }
2226
2227        public int readInt() throws IOException {
2228            mPosition += 4;
2229            if (mPosition > mLength) {
2230                throw new EOFException();
2231            }
2232            int ch1 = super.read();
2233            int ch2 = super.read();
2234            int ch3 = super.read();
2235            int ch4 = super.read();
2236            if ((ch1 | ch2 | ch3 | ch4) < 0) {
2237                throw new EOFException();
2238            }
2239            if (mByteOrder == LITTLE_ENDIAN) {
2240                return ((ch4 << 24) + (ch3 << 16) + (ch2 << 8) + ch1);
2241            } else if (mByteOrder == BIG_ENDIAN) {
2242                return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4);
2243            }
2244            throw new IOException("Invalid byte order: " + mByteOrder);
2245        }
2246
2247        @Override
2248        public long skip(long byteCount) {
2249            long skipped = super.skip(Math.min(byteCount, mLength - mPosition));
2250            mPosition += skipped;
2251            return skipped;
2252        }
2253
2254        public int readUnsignedShort() throws IOException {
2255            mPosition += 2;
2256            if (mPosition > mLength) {
2257                throw new EOFException();
2258            }
2259            int ch1 = super.read();
2260            int ch2 = super.read();
2261            if ((ch1 | ch2) < 0) {
2262                throw new EOFException();
2263            }
2264            if (mByteOrder == LITTLE_ENDIAN) {
2265                return ((ch2 << 8) + (ch1));
2266            } else if (mByteOrder == BIG_ENDIAN) {
2267                return ((ch1 << 8) + (ch2));
2268            }
2269            throw new IOException("Invalid byte order: " + mByteOrder);
2270        }
2271
2272        public long readUnsignedInt() throws IOException {
2273            return readInt() & 0xffffffffL;
2274        }
2275
2276        public long readLong() throws IOException {
2277            mPosition += 8;
2278            if (mPosition > mLength) {
2279                throw new EOFException();
2280            }
2281            int ch1 = super.read();
2282            int ch2 = super.read();
2283            int ch3 = super.read();
2284            int ch4 = super.read();
2285            int ch5 = super.read();
2286            int ch6 = super.read();
2287            int ch7 = super.read();
2288            int ch8 = super.read();
2289            if ((ch1 | ch2 | ch3 | ch4 | ch5 | ch6 | ch7 | ch8) < 0) {
2290                throw new EOFException();
2291            }
2292            if (mByteOrder == LITTLE_ENDIAN) {
2293                return (((long) ch8 << 56) + ((long) ch7 << 48) + ((long) ch6 << 40)
2294                        + ((long) ch5 << 32) + ((long) ch4 << 24) + ((long) ch3 << 16)
2295                        + ((long) ch2 << 8) + (long) ch1);
2296            } else if (mByteOrder == BIG_ENDIAN) {
2297                return (((long) ch1 << 56) + ((long) ch2 << 48) + ((long) ch3 << 40)
2298                        + ((long) ch4 << 32) + ((long) ch5 << 24) + ((long) ch6 << 16)
2299                        + ((long) ch7 << 8) + (long) ch8);
2300            }
2301            throw new IOException("Invalid byte order: " + mByteOrder);
2302        }
2303
2304        public float readFloat() throws IOException {
2305            return Float.intBitsToFloat(readInt());
2306        }
2307
2308        public double readDouble() throws IOException {
2309            return Double.longBitsToDouble(readLong());
2310        }
2311    }
2312
2313    // An output stream to write EXIF data area, that will be written in big endian byte order.
2314    private static class ExifDataOutputStream extends DataOutputStream {
2315        public ExifDataOutputStream(OutputStream out) {
2316            super(out);
2317        }
2318
2319        public void writeUnsignedShort(int val) throws IOException {
2320            writeShort((short) val);
2321        }
2322
2323        public void writeUnsignedInt(long val) throws IOException {
2324            writeInt((int) val);
2325        }
2326    }
2327
2328    // JNI methods for RAW formats.
2329    private static native void nativeInitRaw();
2330    private static native byte[] nativeGetThumbnailFromAsset(
2331            long asset, int thumbnailOffset, int thumbnailLength);
2332    private static native HashMap nativeGetRawAttributesFromAsset(long asset);
2333    private static native HashMap nativeGetRawAttributesFromFileDescriptor(FileDescriptor fd);
2334    private static native HashMap nativeGetRawAttributesFromInputStream(InputStream in);
2335}
2336