• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.media;
18 
19 import android.annotation.CurrentTimeMillisLong;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.compat.annotation.UnsupportedAppUsage;
24 import android.content.res.AssetManager;
25 import android.graphics.Bitmap;
26 import android.graphics.BitmapFactory;
27 import android.os.Build;
28 import android.system.ErrnoException;
29 import android.system.Os;
30 import android.system.OsConstants;
31 import android.util.Log;
32 import android.util.Pair;
33 
34 import com.android.internal.annotations.GuardedBy;
35 import com.android.internal.util.ArrayUtils;
36 
37 import libcore.io.IoUtils;
38 import libcore.io.Streams;
39 
40 import java.io.BufferedInputStream;
41 import java.io.BufferedOutputStream;
42 import java.io.ByteArrayInputStream;
43 import java.io.ByteArrayOutputStream;
44 import java.io.DataInput;
45 import java.io.DataInputStream;
46 import java.io.EOFException;
47 import java.io.File;
48 import java.io.FileDescriptor;
49 import java.io.FileInputStream;
50 import java.io.FileNotFoundException;
51 import java.io.FileOutputStream;
52 import java.io.FilterOutputStream;
53 import java.io.IOException;
54 import java.io.InputStream;
55 import java.io.OutputStream;
56 import java.lang.annotation.Retention;
57 import java.lang.annotation.RetentionPolicy;
58 import java.nio.ByteBuffer;
59 import java.nio.ByteOrder;
60 import java.nio.charset.Charset;
61 import java.text.ParsePosition;
62 import java.text.SimpleDateFormat;
63 import java.util.Arrays;
64 import java.util.Date;
65 import java.util.HashMap;
66 import java.util.HashSet;
67 import java.util.Locale;
68 import java.util.Map;
69 import java.util.Set;
70 import java.util.TimeZone;
71 import java.util.UUID;
72 import java.util.regex.Matcher;
73 import java.util.regex.Pattern;
74 import java.util.zip.CRC32;
75 
76 /**
77  * This is a class for reading and writing Exif tags in a JPEG file or a RAW image file.
78  * <p>
79  * Supported formats are: JPEG, DNG, CR2, NEF, NRW, ARW, RW2, ORF, PEF, SRW, RAF and HEIF.
80  * <p>
81  * Attribute mutation is supported for JPEG image files.
82  * <p>
83  * Note: It is recommended to use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
84  * <a href="{@docRoot}reference/androidx/exifinterface/media/ExifInterface.html">ExifInterface
85  * Library</a> since it is a superset of this class. In addition to the functionalities of this
86  * class, it supports parsing extra metadata such as exposure and data compression information
87  * as well as setting extra metadata such as GPS and datetime information.
88  */
89 public class ExifInterface {
90     private static final String TAG = "ExifInterface";
91     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
92 
93     // The Exif tag names. See Tiff 6.0 Section 3 and Section 8.
94     /** Type is String. */
95     public static final String TAG_ARTIST = "Artist";
96     /** Type is int. */
97     public static final String TAG_BITS_PER_SAMPLE = "BitsPerSample";
98     /** Type is int. */
99     public static final String TAG_COMPRESSION = "Compression";
100     /** Type is String. */
101     public static final String TAG_COPYRIGHT = "Copyright";
102     /** Type is String. */
103     public static final String TAG_DATETIME = "DateTime";
104     /** Type is String. */
105     public static final String TAG_IMAGE_DESCRIPTION = "ImageDescription";
106     /** Type is int. */
107     public static final String TAG_IMAGE_LENGTH = "ImageLength";
108     /** Type is int. */
109     public static final String TAG_IMAGE_WIDTH = "ImageWidth";
110     /** Type is int. */
111     public static final String TAG_JPEG_INTERCHANGE_FORMAT = "JPEGInterchangeFormat";
112     /** Type is int. */
113     public static final String TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = "JPEGInterchangeFormatLength";
114     /** Type is String. */
115     public static final String TAG_MAKE = "Make";
116     /** Type is String. */
117     public static final String TAG_MODEL = "Model";
118     /** Type is int. */
119     public static final String TAG_ORIENTATION = "Orientation";
120     /** Type is int. */
121     public static final String TAG_PHOTOMETRIC_INTERPRETATION = "PhotometricInterpretation";
122     /** Type is int. */
123     public static final String TAG_PLANAR_CONFIGURATION = "PlanarConfiguration";
124     /** Type is rational. */
125     public static final String TAG_PRIMARY_CHROMATICITIES = "PrimaryChromaticities";
126     /** Type is rational. */
127     public static final String TAG_REFERENCE_BLACK_WHITE = "ReferenceBlackWhite";
128     /** Type is int. */
129     public static final String TAG_RESOLUTION_UNIT = "ResolutionUnit";
130     /** Type is int. */
131     public static final String TAG_ROWS_PER_STRIP = "RowsPerStrip";
132     /** Type is int. */
133     public static final String TAG_SAMPLES_PER_PIXEL = "SamplesPerPixel";
134     /** Type is String. */
135     public static final String TAG_SOFTWARE = "Software";
136     /** Type is int. */
137     public static final String TAG_STRIP_BYTE_COUNTS = "StripByteCounts";
138     /** Type is int. */
139     public static final String TAG_STRIP_OFFSETS = "StripOffsets";
140     /** Type is int. */
141     public static final String TAG_TRANSFER_FUNCTION = "TransferFunction";
142     /** Type is rational. */
143     public static final String TAG_WHITE_POINT = "WhitePoint";
144     /** Type is rational. */
145     public static final String TAG_X_RESOLUTION = "XResolution";
146     /** Type is rational. */
147     public static final String TAG_Y_CB_CR_COEFFICIENTS = "YCbCrCoefficients";
148     /** Type is int. */
149     public static final String TAG_Y_CB_CR_POSITIONING = "YCbCrPositioning";
150     /** Type is int. */
151     public static final String TAG_Y_CB_CR_SUB_SAMPLING = "YCbCrSubSampling";
152     /** Type is rational. */
153     public static final String TAG_Y_RESOLUTION = "YResolution";
154     /** Type is rational. */
155     public static final String TAG_APERTURE_VALUE = "ApertureValue";
156     /** Type is rational. */
157     public static final String TAG_BRIGHTNESS_VALUE = "BrightnessValue";
158     /** Type is String. */
159     public static final String TAG_CFA_PATTERN = "CFAPattern";
160     /** Type is int. */
161     public static final String TAG_COLOR_SPACE = "ColorSpace";
162     /** Type is String. */
163     public static final String TAG_COMPONENTS_CONFIGURATION = "ComponentsConfiguration";
164     /** Type is rational. */
165     public static final String TAG_COMPRESSED_BITS_PER_PIXEL = "CompressedBitsPerPixel";
166     /** Type is int. */
167     public static final String TAG_CONTRAST = "Contrast";
168     /** Type is int. */
169     public static final String TAG_CUSTOM_RENDERED = "CustomRendered";
170     /** Type is String. */
171     public static final String TAG_DATETIME_DIGITIZED = "DateTimeDigitized";
172     /** Type is String. */
173     public static final String TAG_DATETIME_ORIGINAL = "DateTimeOriginal";
174     /** Type is String. */
175     public static final String TAG_DEVICE_SETTING_DESCRIPTION = "DeviceSettingDescription";
176     /** Type is double. */
177     public static final String TAG_DIGITAL_ZOOM_RATIO = "DigitalZoomRatio";
178     /** Type is String. */
179     public static final String TAG_EXIF_VERSION = "ExifVersion";
180     /** Type is double. */
181     public static final String TAG_EXPOSURE_BIAS_VALUE = "ExposureBiasValue";
182     /** Type is rational. */
183     public static final String TAG_EXPOSURE_INDEX = "ExposureIndex";
184     /** Type is int. */
185     public static final String TAG_EXPOSURE_MODE = "ExposureMode";
186     /** Type is int. */
187     public static final String TAG_EXPOSURE_PROGRAM = "ExposureProgram";
188     /** Type is double. */
189     public static final String TAG_EXPOSURE_TIME = "ExposureTime";
190     /** Type is double. */
191     public static final String TAG_F_NUMBER = "FNumber";
192     /**
193      * Type is double.
194      *
195      * @deprecated use {@link #TAG_F_NUMBER} instead
196      */
197     @Deprecated
198     public static final String TAG_APERTURE = "FNumber";
199     /** Type is String. */
200     public static final String TAG_FILE_SOURCE = "FileSource";
201     /** Type is int. */
202     public static final String TAG_FLASH = "Flash";
203     /** Type is rational. */
204     public static final String TAG_FLASH_ENERGY = "FlashEnergy";
205     /** Type is String. */
206     public static final String TAG_FLASHPIX_VERSION = "FlashpixVersion";
207     /** Type is rational. */
208     public static final String TAG_FOCAL_LENGTH = "FocalLength";
209     /** Type is int. */
210     public static final String TAG_FOCAL_LENGTH_IN_35MM_FILM = "FocalLengthIn35mmFilm";
211     /** Type is int. */
212     public static final String TAG_FOCAL_PLANE_RESOLUTION_UNIT = "FocalPlaneResolutionUnit";
213     /** Type is rational. */
214     public static final String TAG_FOCAL_PLANE_X_RESOLUTION = "FocalPlaneXResolution";
215     /** Type is rational. */
216     public static final String TAG_FOCAL_PLANE_Y_RESOLUTION = "FocalPlaneYResolution";
217     /** Type is int. */
218     public static final String TAG_GAIN_CONTROL = "GainControl";
219     /** Type is int. */
220     public static final String TAG_ISO_SPEED_RATINGS = "ISOSpeedRatings";
221     /**
222      * Type is int.
223      *
224      * @deprecated use {@link #TAG_ISO_SPEED_RATINGS} instead
225      */
226     @Deprecated
227     public static final String TAG_ISO = "ISOSpeedRatings";
228     /** Type is String. */
229     public static final String TAG_IMAGE_UNIQUE_ID = "ImageUniqueID";
230     /** Type is int. */
231     public static final String TAG_LIGHT_SOURCE = "LightSource";
232     /** Type is String. */
233     public static final String TAG_MAKER_NOTE = "MakerNote";
234     /** Type is rational. */
235     public static final String TAG_MAX_APERTURE_VALUE = "MaxApertureValue";
236     /** Type is int. */
237     public static final String TAG_METERING_MODE = "MeteringMode";
238     /** Type is int. */
239     public static final String TAG_NEW_SUBFILE_TYPE = "NewSubfileType";
240     /** Type is String. */
241     public static final String TAG_OECF = "OECF";
242     /**
243      *  <p>A tag used to record the offset from UTC (the time difference from Universal Time
244      *  Coordinated including daylight saving time) of the time of DateTime tag. The format when
245      *  recording the offset is "±HH:MM". The part of "±" shall be recorded as "+" or "-". When
246      *  the offsets are unknown, all the character spaces except colons (":") should be filled
247      *  with blank characters, or else the Interoperability field should be filled with blank
248      *  characters. The character string length is 7 Bytes including NULL for termination. When
249      *  the field is left blank, it is treated as unknown.</p>
250      *
251      *  <ul>
252      *      <li>Tag = 36880</li>
253      *      <li>Type = String</li>
254      *      <li>Length = 7</li>
255      *      <li>Default = None</li>
256      *  </ul>
257      */
258     public static final String TAG_OFFSET_TIME = "OffsetTime";
259     /**
260      *  <p>A tag used to record the offset from UTC (the time difference from Universal Time
261      *  Coordinated including daylight saving time) of the time of DateTimeOriginal tag. The format
262      *  when recording the offset is "±HH:MM". The part of "±" shall be recorded as "+" or "-". When
263      *  the offsets are unknown, all the character spaces except colons (":") should be filled
264      *  with blank characters, or else the Interoperability field should be filled with blank
265      *  characters. The character string length is 7 Bytes including NULL for termination. When
266      *  the field is left blank, it is treated as unknown.</p>
267      *
268      *  <ul>
269      *      <li>Tag = 36881</li>
270      *      <li>Type = String</li>
271      *      <li>Length = 7</li>
272      *      <li>Default = None</li>
273      *  </ul>
274      */
275     public static final String TAG_OFFSET_TIME_ORIGINAL = "OffsetTimeOriginal";
276     /**
277      *  <p>A tag used to record the offset from UTC (the time difference from Universal Time
278      *  Coordinated including daylight saving time) of the time of DateTimeDigitized tag. The format
279      *  when recording the offset is "±HH:MM". The part of "±" shall be recorded as "+" or "-". When
280      *  the offsets are unknown, all the character spaces except colons (":") should be filled
281      *  with blank characters, or else the Interoperability field should be filled with blank
282      *  characters. The character string length is 7 Bytes including NULL for termination. When
283      *  the field is left blank, it is treated as unknown.</p>
284      *
285      *  <ul>
286      *      <li>Tag = 36882</li>
287      *      <li>Type = String</li>
288      *      <li>Length = 7</li>
289      *      <li>Default = None</li>
290      *  </ul>
291      */
292     public static final String TAG_OFFSET_TIME_DIGITIZED = "OffsetTimeDigitized";
293     /** Type is int. */
294     public static final String TAG_PIXEL_X_DIMENSION = "PixelXDimension";
295     /** Type is int. */
296     public static final String TAG_PIXEL_Y_DIMENSION = "PixelYDimension";
297     /** Type is String. */
298     public static final String TAG_RELATED_SOUND_FILE = "RelatedSoundFile";
299     /** Type is int. */
300     public static final String TAG_SATURATION = "Saturation";
301     /** Type is int. */
302     public static final String TAG_SCENE_CAPTURE_TYPE = "SceneCaptureType";
303     /** Type is String. */
304     public static final String TAG_SCENE_TYPE = "SceneType";
305     /** Type is int. */
306     public static final String TAG_SENSING_METHOD = "SensingMethod";
307     /** Type is int. */
308     public static final String TAG_SHARPNESS = "Sharpness";
309     /** Type is rational. */
310     public static final String TAG_SHUTTER_SPEED_VALUE = "ShutterSpeedValue";
311     /** Type is String. */
312     public static final String TAG_SPATIAL_FREQUENCY_RESPONSE = "SpatialFrequencyResponse";
313     /** Type is String. */
314     public static final String TAG_SPECTRAL_SENSITIVITY = "SpectralSensitivity";
315     /** Type is int. */
316     public static final String TAG_SUBFILE_TYPE = "SubfileType";
317     /** Type is String. */
318     public static final String TAG_SUBSEC_TIME = "SubSecTime";
319     /**
320      * Type is String.
321      *
322      * @deprecated use {@link #TAG_SUBSEC_TIME_DIGITIZED} instead
323      */
324     public static final String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized";
325     /** Type is String. */
326     public static final String TAG_SUBSEC_TIME_DIGITIZED = "SubSecTimeDigitized";
327     /**
328      * Type is String.
329      *
330      * @deprecated use {@link #TAG_SUBSEC_TIME_ORIGINAL} instead
331      */
332     public static final String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal";
333     /** Type is String. */
334     public static final String TAG_SUBSEC_TIME_ORIGINAL = "SubSecTimeOriginal";
335     /** Type is int. */
336     public static final String TAG_SUBJECT_AREA = "SubjectArea";
337     /** Type is double. */
338     public static final String TAG_SUBJECT_DISTANCE = "SubjectDistance";
339     /** Type is int. */
340     public static final String TAG_SUBJECT_DISTANCE_RANGE = "SubjectDistanceRange";
341     /** Type is int. */
342     public static final String TAG_SUBJECT_LOCATION = "SubjectLocation";
343     /** Type is String. */
344     public static final String TAG_USER_COMMENT = "UserComment";
345     /** Type is int. */
346     public static final String TAG_WHITE_BALANCE = "WhiteBalance";
347     /**
348      * The altitude (in meters) based on the reference in TAG_GPS_ALTITUDE_REF.
349      * Type is rational.
350      */
351     public static final String TAG_GPS_ALTITUDE = "GPSAltitude";
352     /**
353      * 0 if the altitude is above sea level. 1 if the altitude is below sea
354      * level. Type is int.
355      */
356     public static final String TAG_GPS_ALTITUDE_REF = "GPSAltitudeRef";
357     /** Type is String. */
358     public static final String TAG_GPS_AREA_INFORMATION = "GPSAreaInformation";
359     /** Type is rational. */
360     public static final String TAG_GPS_DOP = "GPSDOP";
361     /** Type is String. */
362     public static final String TAG_GPS_DATESTAMP = "GPSDateStamp";
363     /** Type is rational. */
364     public static final String TAG_GPS_DEST_BEARING = "GPSDestBearing";
365     /** Type is String. */
366     public static final String TAG_GPS_DEST_BEARING_REF = "GPSDestBearingRef";
367     /** Type is rational. */
368     public static final String TAG_GPS_DEST_DISTANCE = "GPSDestDistance";
369     /** Type is String. */
370     public static final String TAG_GPS_DEST_DISTANCE_REF = "GPSDestDistanceRef";
371     /** Type is rational. */
372     public static final String TAG_GPS_DEST_LATITUDE = "GPSDestLatitude";
373     /** Type is String. */
374     public static final String TAG_GPS_DEST_LATITUDE_REF = "GPSDestLatitudeRef";
375     /** Type is rational. */
376     public static final String TAG_GPS_DEST_LONGITUDE = "GPSDestLongitude";
377     /** Type is String. */
378     public static final String TAG_GPS_DEST_LONGITUDE_REF = "GPSDestLongitudeRef";
379     /** Type is int. */
380     public static final String TAG_GPS_DIFFERENTIAL = "GPSDifferential";
381     /** Type is rational. */
382     public static final String TAG_GPS_IMG_DIRECTION = "GPSImgDirection";
383     /** Type is String. */
384     public static final String TAG_GPS_IMG_DIRECTION_REF = "GPSImgDirectionRef";
385     /** Type is rational. Format is "num1/denom1,num2/denom2,num3/denom3". */
386     public static final String TAG_GPS_LATITUDE = "GPSLatitude";
387     /** Type is String. */
388     public static final String TAG_GPS_LATITUDE_REF = "GPSLatitudeRef";
389     /** Type is rational. Format is "num1/denom1,num2/denom2,num3/denom3". */
390     public static final String TAG_GPS_LONGITUDE = "GPSLongitude";
391     /** Type is String. */
392     public static final String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef";
393     /** Type is String. */
394     public static final String TAG_GPS_MAP_DATUM = "GPSMapDatum";
395     /** Type is String. */
396     public static final String TAG_GPS_MEASURE_MODE = "GPSMeasureMode";
397     /** Type is String. Name of GPS processing method used for location finding. */
398     public static final String TAG_GPS_PROCESSING_METHOD = "GPSProcessingMethod";
399     /** Type is String. */
400     public static final String TAG_GPS_SATELLITES = "GPSSatellites";
401     /** Type is rational. */
402     public static final String TAG_GPS_SPEED = "GPSSpeed";
403     /** Type is String. */
404     public static final String TAG_GPS_SPEED_REF = "GPSSpeedRef";
405     /** Type is String. */
406     public static final String TAG_GPS_STATUS = "GPSStatus";
407     /** Type is String. Format is "hh:mm:ss". */
408     public static final String TAG_GPS_TIMESTAMP = "GPSTimeStamp";
409     /** Type is rational. */
410     public static final String TAG_GPS_TRACK = "GPSTrack";
411     /** Type is String. */
412     public static final String TAG_GPS_TRACK_REF = "GPSTrackRef";
413     /** Type is String. */
414     public static final String TAG_GPS_VERSION_ID = "GPSVersionID";
415     /** Type is String. */
416     public static final String TAG_INTEROPERABILITY_INDEX = "InteroperabilityIndex";
417     /** Type is int. */
418     public static final String TAG_THUMBNAIL_IMAGE_LENGTH = "ThumbnailImageLength";
419     /** Type is int. */
420     public static final String TAG_THUMBNAIL_IMAGE_WIDTH = "ThumbnailImageWidth";
421     /** Type is int. */
422     public static final String TAG_THUMBNAIL_ORIENTATION = "ThumbnailOrientation";
423     /** Type is int. DNG Specification 1.4.0.0. Section 4 */
424     public static final String TAG_DNG_VERSION = "DNGVersion";
425     /** Type is int. DNG Specification 1.4.0.0. Section 4 */
426     public static final String TAG_DEFAULT_CROP_SIZE = "DefaultCropSize";
427     /** Type is undefined. See Olympus MakerNote tags in http://www.exiv2.org/tags-olympus.html. */
428     public static final String TAG_ORF_THUMBNAIL_IMAGE = "ThumbnailImage";
429     /** Type is int. See Olympus Camera Settings tags in http://www.exiv2.org/tags-olympus.html. */
430     public static final String TAG_ORF_PREVIEW_IMAGE_START = "PreviewImageStart";
431     /** Type is int. See Olympus Camera Settings tags in http://www.exiv2.org/tags-olympus.html. */
432     public static final String TAG_ORF_PREVIEW_IMAGE_LENGTH = "PreviewImageLength";
433     /** Type is int. See Olympus Image Processing tags in http://www.exiv2.org/tags-olympus.html. */
434     public static final String TAG_ORF_ASPECT_FRAME = "AspectFrame";
435     /**
436      * Type is int. See PanasonicRaw tags in
437      * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PanasonicRaw.html
438      */
439     public static final String TAG_RW2_SENSOR_BOTTOM_BORDER = "SensorBottomBorder";
440     /**
441      * Type is int. See PanasonicRaw tags in
442      * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PanasonicRaw.html
443      */
444     public static final String TAG_RW2_SENSOR_LEFT_BORDER = "SensorLeftBorder";
445     /**
446      * Type is int. See PanasonicRaw tags in
447      * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PanasonicRaw.html
448      */
449     public static final String TAG_RW2_SENSOR_RIGHT_BORDER = "SensorRightBorder";
450     /**
451      * Type is int. See PanasonicRaw tags in
452      * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PanasonicRaw.html
453      */
454     public static final String TAG_RW2_SENSOR_TOP_BORDER = "SensorTopBorder";
455     /**
456      * Type is int. See PanasonicRaw tags in
457      * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PanasonicRaw.html
458      */
459     public static final String TAG_RW2_ISO = "ISO";
460     /**
461      * Type is undefined. See PanasonicRaw tags in
462      * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PanasonicRaw.html
463      */
464     public static final String TAG_RW2_JPG_FROM_RAW = "JpgFromRaw";
465     /**
466      * Type is byte[]. See <a href=
467      * "https://en.wikipedia.org/wiki/Extensible_Metadata_Platform">Extensible
468      * Metadata Platform (XMP)</a> for details on contents.
469      */
470     public static final String TAG_XMP = "Xmp";
471 
472     /**
473      * Private tags used for pointing the other IFD offsets.
474      * The types of the following tags are int.
475      * See JEITA CP-3451C Section 4.6.3: Exif-specific IFD.
476      * For SubIFD, see Note 1 of Adobe PageMaker® 6.0 TIFF Technical Notes.
477      */
478     private static final String TAG_EXIF_IFD_POINTER = "ExifIFDPointer";
479     private static final String TAG_GPS_INFO_IFD_POINTER = "GPSInfoIFDPointer";
480     private static final String TAG_INTEROPERABILITY_IFD_POINTER = "InteroperabilityIFDPointer";
481     private static final String TAG_SUB_IFD_POINTER = "SubIFDPointer";
482     // Proprietary pointer tags used for ORF files.
483     // See http://www.exiv2.org/tags-olympus.html
484     private static final String TAG_ORF_CAMERA_SETTINGS_IFD_POINTER = "CameraSettingsIFDPointer";
485     private static final String TAG_ORF_IMAGE_PROCESSING_IFD_POINTER = "ImageProcessingIFDPointer";
486 
487     // Private tags used for thumbnail information.
488     private static final String TAG_HAS_THUMBNAIL = "HasThumbnail";
489     private static final String TAG_THUMBNAIL_OFFSET = "ThumbnailOffset";
490     private static final String TAG_THUMBNAIL_LENGTH = "ThumbnailLength";
491     private static final String TAG_THUMBNAIL_DATA = "ThumbnailData";
492     private static final int MAX_THUMBNAIL_SIZE = 512;
493 
494     // Constants used for the Orientation Exif tag.
495     public static final int ORIENTATION_UNDEFINED = 0;
496     public static final int ORIENTATION_NORMAL = 1;
497     public static final int ORIENTATION_FLIP_HORIZONTAL = 2;  // left right reversed mirror
498     public static final int ORIENTATION_ROTATE_180 = 3;
499     public static final int ORIENTATION_FLIP_VERTICAL = 4;  // upside down mirror
500     // flipped about top-left <--> bottom-right axis
501     public static final int ORIENTATION_TRANSPOSE = 5;
502     public static final int ORIENTATION_ROTATE_90 = 6;  // rotate 90 cw to right it
503     // flipped about top-right <--> bottom-left axis
504     public static final int ORIENTATION_TRANSVERSE = 7;
505     public static final int ORIENTATION_ROTATE_270 = 8;  // rotate 270 to right it
506 
507     // Constants used for white balance
508     public static final int WHITEBALANCE_AUTO = 0;
509     public static final int WHITEBALANCE_MANUAL = 1;
510 
511     /**
512      * Constant used to indicate that the input stream contains the full image data.
513      * <p>
514      * The format of the image data should follow one of the image formats supported by this class.
515      */
516     public static final int STREAM_TYPE_FULL_IMAGE_DATA = 0;
517     /**
518      * Constant used to indicate that the input stream contains only Exif data.
519      * <p>
520      * The format of the Exif-only data must follow the below structure:
521      *     Exif Identifier Code ("Exif\0\0") + TIFF header + IFD data
522      * See JEITA CP-3451C Section 4.5.2 and 4.5.4 specifications for more details.
523      */
524     public static final int STREAM_TYPE_EXIF_DATA_ONLY = 1;
525 
526     /** @hide */
527     @Retention(RetentionPolicy.SOURCE)
528     @IntDef({STREAM_TYPE_FULL_IMAGE_DATA, STREAM_TYPE_EXIF_DATA_ONLY})
529     public @interface ExifStreamType {}
530 
531     // Maximum size for checking file type signature (see image_type_recognition_lite.cc)
532     private static final int SIGNATURE_CHECK_SIZE = 5000;
533 
534     private static final byte[] JPEG_SIGNATURE = new byte[] {(byte) 0xff, (byte) 0xd8, (byte) 0xff};
535     private static final String RAF_SIGNATURE = "FUJIFILMCCD-RAW";
536     private static final int RAF_OFFSET_TO_JPEG_IMAGE_OFFSET = 84;
537     private static final int RAF_INFO_SIZE = 160;
538     private static final int RAF_JPEG_LENGTH_VALUE_SIZE = 4;
539 
540     private static final byte[] HEIF_TYPE_FTYP = new byte[] {'f', 't', 'y', 'p'};
541     private static final byte[] HEIF_BRAND_MIF1 = new byte[] {'m', 'i', 'f', '1'};
542     private static final byte[] HEIF_BRAND_HEIC = new byte[] {'h', 'e', 'i', 'c'};
543 
544     // See http://fileformats.archiveteam.org/wiki/Olympus_ORF
545     private static final short ORF_SIGNATURE_1 = 0x4f52;
546     private static final short ORF_SIGNATURE_2 = 0x5352;
547     // There are two formats for Olympus Makernote Headers. Each has different identifiers and
548     // offsets to the actual data.
549     // See http://www.exiv2.org/makernote.html#R1
550     private static final byte[] ORF_MAKER_NOTE_HEADER_1 = new byte[] {(byte) 0x4f, (byte) 0x4c,
551             (byte) 0x59, (byte) 0x4d, (byte) 0x50, (byte) 0x00}; // "OLYMP\0"
552     private static final byte[] ORF_MAKER_NOTE_HEADER_2 = new byte[] {(byte) 0x4f, (byte) 0x4c,
553             (byte) 0x59, (byte) 0x4d, (byte) 0x50, (byte) 0x55, (byte) 0x53, (byte) 0x00,
554             (byte) 0x49, (byte) 0x49}; // "OLYMPUS\0II"
555     private static final int ORF_MAKER_NOTE_HEADER_1_SIZE = 8;
556     private static final int ORF_MAKER_NOTE_HEADER_2_SIZE = 12;
557 
558     // See http://fileformats.archiveteam.org/wiki/RW2
559     private static final short RW2_SIGNATURE = 0x0055;
560 
561     // See http://fileformats.archiveteam.org/wiki/Pentax_PEF
562     private static final String PEF_SIGNATURE = "PENTAX";
563     // See http://www.exiv2.org/makernote.html#R11
564     private static final int PEF_MAKER_NOTE_SKIP_SIZE = 6;
565 
566     // See PNG (Portable Network Graphics) Specification, Version 1.2,
567     // 3.1. PNG file signature
568     private static final byte[] PNG_SIGNATURE = new byte[] {(byte) 0x89, (byte) 0x50, (byte) 0x4e,
569             (byte) 0x47, (byte) 0x0d, (byte) 0x0a, (byte) 0x1a, (byte) 0x0a};
570     // See PNG (Portable Network Graphics) Specification, Version 1.2,
571     // 3.7. eXIf Exchangeable Image File (Exif) Profile
572     private static final byte[] PNG_CHUNK_TYPE_EXIF = new byte[]{(byte) 0x65, (byte) 0x58,
573             (byte) 0x49, (byte) 0x66};
574     private static final byte[] PNG_CHUNK_TYPE_IHDR = new byte[]{(byte) 0x49, (byte) 0x48,
575             (byte) 0x44, (byte) 0x52};
576     private static final byte[] PNG_CHUNK_TYPE_IEND = new byte[]{(byte) 0x49, (byte) 0x45,
577             (byte) 0x4e, (byte) 0x44};
578     private static final int PNG_CHUNK_TYPE_BYTE_LENGTH = 4;
579     private static final int PNG_CHUNK_CRC_BYTE_LENGTH = 4;
580 
581     // See https://developers.google.com/speed/webp/docs/riff_container, Section "WebP File Header"
582     private static final byte[] WEBP_SIGNATURE_1 = new byte[] {'R', 'I', 'F', 'F'};
583     private static final byte[] WEBP_SIGNATURE_2 = new byte[] {'W', 'E', 'B', 'P'};
584     private static final int WEBP_FILE_SIZE_BYTE_LENGTH = 4;
585     private static final byte[] WEBP_CHUNK_TYPE_EXIF = new byte[]{(byte) 0x45, (byte) 0x58,
586             (byte) 0x49, (byte) 0x46};
587     private static final int WEBP_CHUNK_TYPE_BYTE_LENGTH = 4;
588     private static final int WEBP_CHUNK_SIZE_BYTE_LENGTH = 4;
589 
590     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
591     @GuardedBy("sFormatter")
592     private static SimpleDateFormat sFormatter;
593     @GuardedBy("sFormatterTz")
594     private static SimpleDateFormat sFormatterTz;
595 
596     // See Exchangeable image file format for digital still cameras: Exif version 2.2.
597     // The following values are for parsing EXIF data area. There are tag groups in EXIF data area.
598     // They are called "Image File Directory". They have multiple data formats to cover various
599     // image metadata from GPS longitude to camera model name.
600 
601     // Types of Exif byte alignments (see JEITA CP-3451C Section 4.5.2)
602     private static final short BYTE_ALIGN_II = 0x4949;  // II: Intel order
603     private static final short BYTE_ALIGN_MM = 0x4d4d;  // MM: Motorola order
604 
605     // TIFF Header Fixed Constant (see JEITA CP-3451C Section 4.5.2)
606     private static final byte START_CODE = 0x2a; // 42
607     private static final int IFD_OFFSET = 8;
608 
609     // Formats for the value in IFD entry (See TIFF 6.0 Section 2, "Image File Directory".)
610     private static final int IFD_FORMAT_BYTE = 1;
611     private static final int IFD_FORMAT_STRING = 2;
612     private static final int IFD_FORMAT_USHORT = 3;
613     private static final int IFD_FORMAT_ULONG = 4;
614     private static final int IFD_FORMAT_URATIONAL = 5;
615     private static final int IFD_FORMAT_SBYTE = 6;
616     private static final int IFD_FORMAT_UNDEFINED = 7;
617     private static final int IFD_FORMAT_SSHORT = 8;
618     private static final int IFD_FORMAT_SLONG = 9;
619     private static final int IFD_FORMAT_SRATIONAL = 10;
620     private static final int IFD_FORMAT_SINGLE = 11;
621     private static final int IFD_FORMAT_DOUBLE = 12;
622     // Format indicating a new IFD entry (See Adobe PageMaker® 6.0 TIFF Technical Notes, "New Tag")
623     private static final int IFD_FORMAT_IFD = 13;
624     // Names for the data formats for debugging purpose.
625     private static final String[] IFD_FORMAT_NAMES = new String[] {
626             "", "BYTE", "STRING", "USHORT", "ULONG", "URATIONAL", "SBYTE", "UNDEFINED", "SSHORT",
627             "SLONG", "SRATIONAL", "SINGLE", "DOUBLE", "IFD"
628     };
629     // Sizes of the components of each IFD value format
630     private static final int[] IFD_FORMAT_BYTES_PER_FORMAT = new int[] {
631             0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8, 1
632     };
633     private static final byte[] EXIF_ASCII_PREFIX = new byte[] {
634             0x41, 0x53, 0x43, 0x49, 0x49, 0x0, 0x0, 0x0
635     };
636 
637     /**
638      * Constants used for Compression tag.
639      * For Value 1, 2, 32773, see TIFF 6.0 Spec Section 3: Bilevel Images, Compression
640      * For Value 6, see TIFF 6.0 Spec Section 22: JPEG Compression, Extensions to Existing Fields
641      * For Value 7, 8, 34892, see DNG Specification 1.4.0.0. Section 3, Compression
642      */
643     private static final int DATA_UNCOMPRESSED = 1;
644     private static final int DATA_HUFFMAN_COMPRESSED = 2;
645     private static final int DATA_JPEG = 6;
646     private static final int DATA_JPEG_COMPRESSED = 7;
647     private static final int DATA_DEFLATE_ZIP = 8;
648     private static final int DATA_PACK_BITS_COMPRESSED = 32773;
649     private static final int DATA_LOSSY_JPEG = 34892;
650 
651     /**
652      * Constants used for BitsPerSample tag.
653      * For RGB, see TIFF 6.0 Spec Section 6, Differences from Palette Color Images
654      * For Greyscale, see TIFF 6.0 Spec Section 4, Differences from Bilevel Images
655      */
656     private static final int[] BITS_PER_SAMPLE_RGB = new int[] { 8, 8, 8 };
657     private static final int[] BITS_PER_SAMPLE_GREYSCALE_1 = new int[] { 4 };
658     private static final int[] BITS_PER_SAMPLE_GREYSCALE_2 = new int[] { 8 };
659 
660     /**
661      * Constants used for PhotometricInterpretation tag.
662      * For White/Black, see Section 3, Color.
663      * See TIFF 6.0 Spec Section 22, Minimum Requirements for TIFF with JPEG Compression.
664      */
665     private static final int PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO = 0;
666     private static final int PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO = 1;
667     private static final int PHOTOMETRIC_INTERPRETATION_RGB = 2;
668     private static final int PHOTOMETRIC_INTERPRETATION_YCBCR = 6;
669 
670     /**
671      * Constants used for NewSubfileType tag.
672      * See TIFF 6.0 Spec Section 8
673      * */
674     private static final int ORIGINAL_RESOLUTION_IMAGE = 0;
675     private static final int REDUCED_RESOLUTION_IMAGE = 1;
676 
677     // A class for indicating EXIF rational type.
678     private static class Rational {
679         public final long numerator;
680         public final long denominator;
681 
Rational(long numerator, long denominator)682         private Rational(long numerator, long denominator) {
683             // Handle erroneous case
684             if (denominator == 0) {
685                 this.numerator = 0;
686                 this.denominator = 1;
687                 return;
688             }
689             this.numerator = numerator;
690             this.denominator = denominator;
691         }
692 
693         @Override
toString()694         public String toString() {
695             return numerator + "/" + denominator;
696         }
697 
calculate()698         public double calculate() {
699             return (double) numerator / denominator;
700         }
701     }
702 
703     // A class for indicating EXIF attribute.
704     private static class ExifAttribute {
705         public final int format;
706         public final int numberOfComponents;
707         public final long bytesOffset;
708         public final byte[] bytes;
709 
710         public static final long BYTES_OFFSET_UNKNOWN = -1;
711 
ExifAttribute(int format, int numberOfComponents, byte[] bytes)712         private ExifAttribute(int format, int numberOfComponents, byte[] bytes) {
713             this(format, numberOfComponents, BYTES_OFFSET_UNKNOWN, bytes);
714         }
715 
ExifAttribute(int format, int numberOfComponents, long bytesOffset, byte[] bytes)716         private ExifAttribute(int format, int numberOfComponents, long bytesOffset, byte[] bytes) {
717             this.format = format;
718             this.numberOfComponents = numberOfComponents;
719             this.bytesOffset = bytesOffset;
720             this.bytes = bytes;
721         }
722 
createUShort(int[] values, ByteOrder byteOrder)723         public static ExifAttribute createUShort(int[] values, ByteOrder byteOrder) {
724             final ByteBuffer buffer = ByteBuffer.wrap(
725                     new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_USHORT] * values.length]);
726             buffer.order(byteOrder);
727             for (int value : values) {
728                 buffer.putShort((short) value);
729             }
730             return new ExifAttribute(IFD_FORMAT_USHORT, values.length, buffer.array());
731         }
732 
createUShort(int value, ByteOrder byteOrder)733         public static ExifAttribute createUShort(int value, ByteOrder byteOrder) {
734             return createUShort(new int[] {value}, byteOrder);
735         }
736 
createULong(long[] values, ByteOrder byteOrder)737         public static ExifAttribute createULong(long[] values, ByteOrder byteOrder) {
738             final ByteBuffer buffer = ByteBuffer.wrap(
739                     new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_ULONG] * values.length]);
740             buffer.order(byteOrder);
741             for (long value : values) {
742                 buffer.putInt((int) value);
743             }
744             return new ExifAttribute(IFD_FORMAT_ULONG, values.length, buffer.array());
745         }
746 
createULong(long value, ByteOrder byteOrder)747         public static ExifAttribute createULong(long value, ByteOrder byteOrder) {
748             return createULong(new long[] {value}, byteOrder);
749         }
750 
createSLong(int[] values, ByteOrder byteOrder)751         public static ExifAttribute createSLong(int[] values, ByteOrder byteOrder) {
752             final ByteBuffer buffer = ByteBuffer.wrap(
753                     new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_SLONG] * values.length]);
754             buffer.order(byteOrder);
755             for (int value : values) {
756                 buffer.putInt(value);
757             }
758             return new ExifAttribute(IFD_FORMAT_SLONG, values.length, buffer.array());
759         }
760 
createSLong(int value, ByteOrder byteOrder)761         public static ExifAttribute createSLong(int value, ByteOrder byteOrder) {
762             return createSLong(new int[] {value}, byteOrder);
763         }
764 
createByte(String value)765         public static ExifAttribute createByte(String value) {
766             // Exception for GPSAltitudeRef tag
767             if (value.length() == 1 && value.charAt(0) >= '0' && value.charAt(0) <= '1') {
768                 final byte[] bytes = new byte[] { (byte) (value.charAt(0) - '0') };
769                 return new ExifAttribute(IFD_FORMAT_BYTE, bytes.length, bytes);
770             }
771             final byte[] ascii = value.getBytes(ASCII);
772             return new ExifAttribute(IFD_FORMAT_BYTE, ascii.length, ascii);
773         }
774 
createString(String value)775         public static ExifAttribute createString(String value) {
776             final byte[] ascii = (value + '\0').getBytes(ASCII);
777             return new ExifAttribute(IFD_FORMAT_STRING, ascii.length, ascii);
778         }
779 
createURational(Rational[] values, ByteOrder byteOrder)780         public static ExifAttribute createURational(Rational[] values, ByteOrder byteOrder) {
781             final ByteBuffer buffer = ByteBuffer.wrap(
782                     new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_URATIONAL] * values.length]);
783             buffer.order(byteOrder);
784             for (Rational value : values) {
785                 buffer.putInt((int) value.numerator);
786                 buffer.putInt((int) value.denominator);
787             }
788             return new ExifAttribute(IFD_FORMAT_URATIONAL, values.length, buffer.array());
789         }
790 
createURational(Rational value, ByteOrder byteOrder)791         public static ExifAttribute createURational(Rational value, ByteOrder byteOrder) {
792             return createURational(new Rational[] {value}, byteOrder);
793         }
794 
createSRational(Rational[] values, ByteOrder byteOrder)795         public static ExifAttribute createSRational(Rational[] values, ByteOrder byteOrder) {
796             final ByteBuffer buffer = ByteBuffer.wrap(
797                     new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_SRATIONAL] * values.length]);
798             buffer.order(byteOrder);
799             for (Rational value : values) {
800                 buffer.putInt((int) value.numerator);
801                 buffer.putInt((int) value.denominator);
802             }
803             return new ExifAttribute(IFD_FORMAT_SRATIONAL, values.length, buffer.array());
804         }
805 
createSRational(Rational value, ByteOrder byteOrder)806         public static ExifAttribute createSRational(Rational value, ByteOrder byteOrder) {
807             return createSRational(new Rational[] {value}, byteOrder);
808         }
809 
createDouble(double[] values, ByteOrder byteOrder)810         public static ExifAttribute createDouble(double[] values, ByteOrder byteOrder) {
811             final ByteBuffer buffer = ByteBuffer.wrap(
812                     new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_DOUBLE] * values.length]);
813             buffer.order(byteOrder);
814             for (double value : values) {
815                 buffer.putDouble(value);
816             }
817             return new ExifAttribute(IFD_FORMAT_DOUBLE, values.length, buffer.array());
818         }
819 
createDouble(double value, ByteOrder byteOrder)820         public static ExifAttribute createDouble(double value, ByteOrder byteOrder) {
821             return createDouble(new double[] {value}, byteOrder);
822         }
823 
824         @Override
toString()825         public String toString() {
826             return "(" + IFD_FORMAT_NAMES[format] + ", data length:" + bytes.length + ")";
827         }
828 
getValue(ByteOrder byteOrder)829         private Object getValue(ByteOrder byteOrder) {
830             try {
831                 ByteOrderedDataInputStream inputStream =
832                         new ByteOrderedDataInputStream(bytes);
833                 inputStream.setByteOrder(byteOrder);
834                 switch (format) {
835                     case IFD_FORMAT_BYTE:
836                     case IFD_FORMAT_SBYTE: {
837                         // Exception for GPSAltitudeRef tag
838                         if (bytes.length == 1 && bytes[0] >= 0 && bytes[0] <= 1) {
839                             return new String(new char[] { (char) (bytes[0] + '0') });
840                         }
841                         return new String(bytes, ASCII);
842                     }
843                     case IFD_FORMAT_UNDEFINED:
844                     case IFD_FORMAT_STRING: {
845                         int index = 0;
846                         if (numberOfComponents >= EXIF_ASCII_PREFIX.length) {
847                             boolean same = true;
848                             for (int i = 0; i < EXIF_ASCII_PREFIX.length; ++i) {
849                                 if (bytes[i] != EXIF_ASCII_PREFIX[i]) {
850                                     same = false;
851                                     break;
852                                 }
853                             }
854                             if (same) {
855                                 index = EXIF_ASCII_PREFIX.length;
856                             }
857                         }
858 
859                         StringBuilder stringBuilder = new StringBuilder();
860                         while (index < numberOfComponents) {
861                             int ch = bytes[index];
862                             if (ch == 0) {
863                                 break;
864                             }
865                             if (ch >= 32) {
866                                 stringBuilder.append((char) ch);
867                             } else {
868                                 stringBuilder.append('?');
869                             }
870                             ++index;
871                         }
872                         return stringBuilder.toString();
873                     }
874                     case IFD_FORMAT_USHORT: {
875                         final int[] values = new int[numberOfComponents];
876                         for (int i = 0; i < numberOfComponents; ++i) {
877                             values[i] = inputStream.readUnsignedShort();
878                         }
879                         return values;
880                     }
881                     case IFD_FORMAT_ULONG: {
882                         final long[] values = new long[numberOfComponents];
883                         for (int i = 0; i < numberOfComponents; ++i) {
884                             values[i] = inputStream.readUnsignedInt();
885                         }
886                         return values;
887                     }
888                     case IFD_FORMAT_URATIONAL: {
889                         final Rational[] values = new Rational[numberOfComponents];
890                         for (int i = 0; i < numberOfComponents; ++i) {
891                             final long numerator = inputStream.readUnsignedInt();
892                             final long denominator = inputStream.readUnsignedInt();
893                             values[i] = new Rational(numerator, denominator);
894                         }
895                         return values;
896                     }
897                     case IFD_FORMAT_SSHORT: {
898                         final int[] values = new int[numberOfComponents];
899                         for (int i = 0; i < numberOfComponents; ++i) {
900                             values[i] = inputStream.readShort();
901                         }
902                         return values;
903                     }
904                     case IFD_FORMAT_SLONG: {
905                         final int[] values = new int[numberOfComponents];
906                         for (int i = 0; i < numberOfComponents; ++i) {
907                             values[i] = inputStream.readInt();
908                         }
909                         return values;
910                     }
911                     case IFD_FORMAT_SRATIONAL: {
912                         final Rational[] values = new Rational[numberOfComponents];
913                         for (int i = 0; i < numberOfComponents; ++i) {
914                             final long numerator = inputStream.readInt();
915                             final long denominator = inputStream.readInt();
916                             values[i] = new Rational(numerator, denominator);
917                         }
918                         return values;
919                     }
920                     case IFD_FORMAT_SINGLE: {
921                         final double[] values = new double[numberOfComponents];
922                         for (int i = 0; i < numberOfComponents; ++i) {
923                             values[i] = inputStream.readFloat();
924                         }
925                         return values;
926                     }
927                     case IFD_FORMAT_DOUBLE: {
928                         final double[] values = new double[numberOfComponents];
929                         for (int i = 0; i < numberOfComponents; ++i) {
930                             values[i] = inputStream.readDouble();
931                         }
932                         return values;
933                     }
934                     default:
935                         return null;
936                 }
937             } catch (IOException e) {
938                 Log.w(TAG, "IOException occurred during reading a value", e);
939                 return null;
940             }
941         }
942 
getDoubleValue(ByteOrder byteOrder)943         public double getDoubleValue(ByteOrder byteOrder) {
944             Object value = getValue(byteOrder);
945             if (value == null) {
946                 throw new NumberFormatException("NULL can't be converted to a double value");
947             }
948             if (value instanceof String) {
949                 return Double.parseDouble((String) value);
950             }
951             if (value instanceof long[]) {
952                 long[] array = (long[]) value;
953                 if (array.length == 1) {
954                     return array[0];
955                 }
956                 throw new NumberFormatException("There are more than one component");
957             }
958             if (value instanceof int[]) {
959                 int[] array = (int[]) value;
960                 if (array.length == 1) {
961                     return array[0];
962                 }
963                 throw new NumberFormatException("There are more than one component");
964             }
965             if (value instanceof double[]) {
966                 double[] array = (double[]) value;
967                 if (array.length == 1) {
968                     return array[0];
969                 }
970                 throw new NumberFormatException("There are more than one component");
971             }
972             if (value instanceof Rational[]) {
973                 Rational[] array = (Rational[]) value;
974                 if (array.length == 1) {
975                     return array[0].calculate();
976                 }
977                 throw new NumberFormatException("There are more than one component");
978             }
979             throw new NumberFormatException("Couldn't find a double value");
980         }
981 
getIntValue(ByteOrder byteOrder)982         public int getIntValue(ByteOrder byteOrder) {
983             Object value = getValue(byteOrder);
984             if (value == null) {
985                 throw new NumberFormatException("NULL can't be converted to a integer value");
986             }
987             if (value instanceof String) {
988                 return Integer.parseInt((String) value);
989             }
990             if (value instanceof long[]) {
991                 long[] array = (long[]) value;
992                 if (array.length == 1) {
993                     return (int) array[0];
994                 }
995                 throw new NumberFormatException("There are more than one component");
996             }
997             if (value instanceof int[]) {
998                 int[] array = (int[]) value;
999                 if (array.length == 1) {
1000                     return array[0];
1001                 }
1002                 throw new NumberFormatException("There are more than one component");
1003             }
1004             throw new NumberFormatException("Couldn't find a integer value");
1005         }
1006 
getStringValue(ByteOrder byteOrder)1007         public String getStringValue(ByteOrder byteOrder) {
1008             Object value = getValue(byteOrder);
1009             if (value == null) {
1010                 return null;
1011             }
1012             if (value instanceof String) {
1013                 return (String) value;
1014             }
1015 
1016             final StringBuilder stringBuilder = new StringBuilder();
1017             if (value instanceof long[]) {
1018                 long[] array = (long[]) value;
1019                 for (int i = 0; i < array.length; ++i) {
1020                     stringBuilder.append(array[i]);
1021                     if (i + 1 != array.length) {
1022                         stringBuilder.append(",");
1023                     }
1024                 }
1025                 return stringBuilder.toString();
1026             }
1027             if (value instanceof int[]) {
1028                 int[] array = (int[]) value;
1029                 for (int i = 0; i < array.length; ++i) {
1030                     stringBuilder.append(array[i]);
1031                     if (i + 1 != array.length) {
1032                         stringBuilder.append(",");
1033                     }
1034                 }
1035                 return stringBuilder.toString();
1036             }
1037             if (value instanceof double[]) {
1038                 double[] array = (double[]) value;
1039                 for (int i = 0; i < array.length; ++i) {
1040                     stringBuilder.append(array[i]);
1041                     if (i + 1 != array.length) {
1042                         stringBuilder.append(",");
1043                     }
1044                 }
1045                 return stringBuilder.toString();
1046             }
1047             if (value instanceof Rational[]) {
1048                 Rational[] array = (Rational[]) value;
1049                 for (int i = 0; i < array.length; ++i) {
1050                     stringBuilder.append(array[i].numerator);
1051                     stringBuilder.append('/');
1052                     stringBuilder.append(array[i].denominator);
1053                     if (i + 1 != array.length) {
1054                         stringBuilder.append(",");
1055                     }
1056                 }
1057                 return stringBuilder.toString();
1058             }
1059             return null;
1060         }
1061 
size()1062         public int size() {
1063             return IFD_FORMAT_BYTES_PER_FORMAT[format] * numberOfComponents;
1064         }
1065     }
1066 
1067     // A class for indicating EXIF tag.
1068     private static class ExifTag {
1069         public final int number;
1070         public final String name;
1071         public final int primaryFormat;
1072         public final int secondaryFormat;
1073 
ExifTag(String name, int number, int format)1074         private ExifTag(String name, int number, int format) {
1075             this.name = name;
1076             this.number = number;
1077             this.primaryFormat = format;
1078             this.secondaryFormat = -1;
1079         }
1080 
ExifTag(String name, int number, int primaryFormat, int secondaryFormat)1081         private ExifTag(String name, int number, int primaryFormat, int secondaryFormat) {
1082             this.name = name;
1083             this.number = number;
1084             this.primaryFormat = primaryFormat;
1085             this.secondaryFormat = secondaryFormat;
1086         }
1087     }
1088 
1089     // Primary image IFD TIFF tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels)
1090     private static final ExifTag[] IFD_TIFF_TAGS = new ExifTag[] {
1091             // For below two, see TIFF 6.0 Spec Section 3: Bilevel Images.
1092             new ExifTag(TAG_NEW_SUBFILE_TYPE, 254, IFD_FORMAT_ULONG),
1093             new ExifTag(TAG_SUBFILE_TYPE, 255, IFD_FORMAT_ULONG),
1094             new ExifTag(TAG_IMAGE_WIDTH, 256, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
1095             new ExifTag(TAG_IMAGE_LENGTH, 257, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
1096             new ExifTag(TAG_BITS_PER_SAMPLE, 258, IFD_FORMAT_USHORT),
1097             new ExifTag(TAG_COMPRESSION, 259, IFD_FORMAT_USHORT),
1098             new ExifTag(TAG_PHOTOMETRIC_INTERPRETATION, 262, IFD_FORMAT_USHORT),
1099             new ExifTag(TAG_IMAGE_DESCRIPTION, 270, IFD_FORMAT_STRING),
1100             new ExifTag(TAG_MAKE, 271, IFD_FORMAT_STRING),
1101             new ExifTag(TAG_MODEL, 272, IFD_FORMAT_STRING),
1102             new ExifTag(TAG_STRIP_OFFSETS, 273, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
1103             new ExifTag(TAG_ORIENTATION, 274, IFD_FORMAT_USHORT),
1104             new ExifTag(TAG_SAMPLES_PER_PIXEL, 277, IFD_FORMAT_USHORT),
1105             new ExifTag(TAG_ROWS_PER_STRIP, 278, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
1106             new ExifTag(TAG_STRIP_BYTE_COUNTS, 279, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
1107             new ExifTag(TAG_X_RESOLUTION, 282, IFD_FORMAT_URATIONAL),
1108             new ExifTag(TAG_Y_RESOLUTION, 283, IFD_FORMAT_URATIONAL),
1109             new ExifTag(TAG_PLANAR_CONFIGURATION, 284, IFD_FORMAT_USHORT),
1110             new ExifTag(TAG_RESOLUTION_UNIT, 296, IFD_FORMAT_USHORT),
1111             new ExifTag(TAG_TRANSFER_FUNCTION, 301, IFD_FORMAT_USHORT),
1112             new ExifTag(TAG_SOFTWARE, 305, IFD_FORMAT_STRING),
1113             new ExifTag(TAG_DATETIME, 306, IFD_FORMAT_STRING),
1114             new ExifTag(TAG_ARTIST, 315, IFD_FORMAT_STRING),
1115             new ExifTag(TAG_WHITE_POINT, 318, IFD_FORMAT_URATIONAL),
1116             new ExifTag(TAG_PRIMARY_CHROMATICITIES, 319, IFD_FORMAT_URATIONAL),
1117             // See Adobe PageMaker® 6.0 TIFF Technical Notes, Note 1.
1118             new ExifTag(TAG_SUB_IFD_POINTER, 330, IFD_FORMAT_ULONG),
1119             new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT, 513, IFD_FORMAT_ULONG),
1120             new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, 514, IFD_FORMAT_ULONG),
1121             new ExifTag(TAG_Y_CB_CR_COEFFICIENTS, 529, IFD_FORMAT_URATIONAL),
1122             new ExifTag(TAG_Y_CB_CR_SUB_SAMPLING, 530, IFD_FORMAT_USHORT),
1123             new ExifTag(TAG_Y_CB_CR_POSITIONING, 531, IFD_FORMAT_USHORT),
1124             new ExifTag(TAG_REFERENCE_BLACK_WHITE, 532, IFD_FORMAT_URATIONAL),
1125             new ExifTag(TAG_COPYRIGHT, 33432, IFD_FORMAT_STRING),
1126             new ExifTag(TAG_EXIF_IFD_POINTER, 34665, IFD_FORMAT_ULONG),
1127             new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG),
1128             // RW2 file tags
1129             // See http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PanasonicRaw.html)
1130             new ExifTag(TAG_RW2_SENSOR_TOP_BORDER, 4, IFD_FORMAT_ULONG),
1131             new ExifTag(TAG_RW2_SENSOR_LEFT_BORDER, 5, IFD_FORMAT_ULONG),
1132             new ExifTag(TAG_RW2_SENSOR_BOTTOM_BORDER, 6, IFD_FORMAT_ULONG),
1133             new ExifTag(TAG_RW2_SENSOR_RIGHT_BORDER, 7, IFD_FORMAT_ULONG),
1134             new ExifTag(TAG_RW2_ISO, 23, IFD_FORMAT_USHORT),
1135             new ExifTag(TAG_RW2_JPG_FROM_RAW, 46, IFD_FORMAT_UNDEFINED),
1136             new ExifTag(TAG_XMP, 700, IFD_FORMAT_BYTE),
1137     };
1138 
1139     // Primary image IFD Exif Private tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels)
1140     private static final ExifTag[] IFD_EXIF_TAGS = new ExifTag[] {
1141             new ExifTag(TAG_EXPOSURE_TIME, 33434, IFD_FORMAT_URATIONAL),
1142             new ExifTag(TAG_F_NUMBER, 33437, IFD_FORMAT_URATIONAL),
1143             new ExifTag(TAG_EXPOSURE_PROGRAM, 34850, IFD_FORMAT_USHORT),
1144             new ExifTag(TAG_SPECTRAL_SENSITIVITY, 34852, IFD_FORMAT_STRING),
1145             new ExifTag(TAG_ISO_SPEED_RATINGS, 34855, IFD_FORMAT_USHORT),
1146             new ExifTag(TAG_OECF, 34856, IFD_FORMAT_UNDEFINED),
1147             new ExifTag(TAG_EXIF_VERSION, 36864, IFD_FORMAT_STRING),
1148             new ExifTag(TAG_DATETIME_ORIGINAL, 36867, IFD_FORMAT_STRING),
1149             new ExifTag(TAG_DATETIME_DIGITIZED, 36868, IFD_FORMAT_STRING),
1150             new ExifTag(TAG_OFFSET_TIME, 36880, IFD_FORMAT_STRING),
1151             new ExifTag(TAG_OFFSET_TIME_ORIGINAL, 36881, IFD_FORMAT_STRING),
1152             new ExifTag(TAG_OFFSET_TIME_DIGITIZED, 36882, IFD_FORMAT_STRING),
1153             new ExifTag(TAG_COMPONENTS_CONFIGURATION, 37121, IFD_FORMAT_UNDEFINED),
1154             new ExifTag(TAG_COMPRESSED_BITS_PER_PIXEL, 37122, IFD_FORMAT_URATIONAL),
1155             new ExifTag(TAG_SHUTTER_SPEED_VALUE, 37377, IFD_FORMAT_SRATIONAL),
1156             new ExifTag(TAG_APERTURE_VALUE, 37378, IFD_FORMAT_URATIONAL),
1157             new ExifTag(TAG_BRIGHTNESS_VALUE, 37379, IFD_FORMAT_SRATIONAL),
1158             new ExifTag(TAG_EXPOSURE_BIAS_VALUE, 37380, IFD_FORMAT_SRATIONAL),
1159             new ExifTag(TAG_MAX_APERTURE_VALUE, 37381, IFD_FORMAT_URATIONAL),
1160             new ExifTag(TAG_SUBJECT_DISTANCE, 37382, IFD_FORMAT_URATIONAL),
1161             new ExifTag(TAG_METERING_MODE, 37383, IFD_FORMAT_USHORT),
1162             new ExifTag(TAG_LIGHT_SOURCE, 37384, IFD_FORMAT_USHORT),
1163             new ExifTag(TAG_FLASH, 37385, IFD_FORMAT_USHORT),
1164             new ExifTag(TAG_FOCAL_LENGTH, 37386, IFD_FORMAT_URATIONAL),
1165             new ExifTag(TAG_SUBJECT_AREA, 37396, IFD_FORMAT_USHORT),
1166             new ExifTag(TAG_MAKER_NOTE, 37500, IFD_FORMAT_UNDEFINED),
1167             new ExifTag(TAG_USER_COMMENT, 37510, IFD_FORMAT_UNDEFINED),
1168             new ExifTag(TAG_SUBSEC_TIME, 37520, IFD_FORMAT_STRING),
1169             new ExifTag(TAG_SUBSEC_TIME_ORIG, 37521, IFD_FORMAT_STRING),
1170             new ExifTag(TAG_SUBSEC_TIME_DIG, 37522, IFD_FORMAT_STRING),
1171             new ExifTag(TAG_FLASHPIX_VERSION, 40960, IFD_FORMAT_UNDEFINED),
1172             new ExifTag(TAG_COLOR_SPACE, 40961, IFD_FORMAT_USHORT),
1173             new ExifTag(TAG_PIXEL_X_DIMENSION, 40962, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
1174             new ExifTag(TAG_PIXEL_Y_DIMENSION, 40963, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
1175             new ExifTag(TAG_RELATED_SOUND_FILE, 40964, IFD_FORMAT_STRING),
1176             new ExifTag(TAG_INTEROPERABILITY_IFD_POINTER, 40965, IFD_FORMAT_ULONG),
1177             new ExifTag(TAG_FLASH_ENERGY, 41483, IFD_FORMAT_URATIONAL),
1178             new ExifTag(TAG_SPATIAL_FREQUENCY_RESPONSE, 41484, IFD_FORMAT_UNDEFINED),
1179             new ExifTag(TAG_FOCAL_PLANE_X_RESOLUTION, 41486, IFD_FORMAT_URATIONAL),
1180             new ExifTag(TAG_FOCAL_PLANE_Y_RESOLUTION, 41487, IFD_FORMAT_URATIONAL),
1181             new ExifTag(TAG_FOCAL_PLANE_RESOLUTION_UNIT, 41488, IFD_FORMAT_USHORT),
1182             new ExifTag(TAG_SUBJECT_LOCATION, 41492, IFD_FORMAT_USHORT),
1183             new ExifTag(TAG_EXPOSURE_INDEX, 41493, IFD_FORMAT_URATIONAL),
1184             new ExifTag(TAG_SENSING_METHOD, 41495, IFD_FORMAT_USHORT),
1185             new ExifTag(TAG_FILE_SOURCE, 41728, IFD_FORMAT_UNDEFINED),
1186             new ExifTag(TAG_SCENE_TYPE, 41729, IFD_FORMAT_UNDEFINED),
1187             new ExifTag(TAG_CFA_PATTERN, 41730, IFD_FORMAT_UNDEFINED),
1188             new ExifTag(TAG_CUSTOM_RENDERED, 41985, IFD_FORMAT_USHORT),
1189             new ExifTag(TAG_EXPOSURE_MODE, 41986, IFD_FORMAT_USHORT),
1190             new ExifTag(TAG_WHITE_BALANCE, 41987, IFD_FORMAT_USHORT),
1191             new ExifTag(TAG_DIGITAL_ZOOM_RATIO, 41988, IFD_FORMAT_URATIONAL),
1192             new ExifTag(TAG_FOCAL_LENGTH_IN_35MM_FILM, 41989, IFD_FORMAT_USHORT),
1193             new ExifTag(TAG_SCENE_CAPTURE_TYPE, 41990, IFD_FORMAT_USHORT),
1194             new ExifTag(TAG_GAIN_CONTROL, 41991, IFD_FORMAT_USHORT),
1195             new ExifTag(TAG_CONTRAST, 41992, IFD_FORMAT_USHORT),
1196             new ExifTag(TAG_SATURATION, 41993, IFD_FORMAT_USHORT),
1197             new ExifTag(TAG_SHARPNESS, 41994, IFD_FORMAT_USHORT),
1198             new ExifTag(TAG_DEVICE_SETTING_DESCRIPTION, 41995, IFD_FORMAT_UNDEFINED),
1199             new ExifTag(TAG_SUBJECT_DISTANCE_RANGE, 41996, IFD_FORMAT_USHORT),
1200             new ExifTag(TAG_IMAGE_UNIQUE_ID, 42016, IFD_FORMAT_STRING),
1201             new ExifTag(TAG_DNG_VERSION, 50706, IFD_FORMAT_BYTE),
1202             new ExifTag(TAG_DEFAULT_CROP_SIZE, 50720, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG)
1203     };
1204 
1205     // Primary image IFD GPS Info tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels)
1206     private static final ExifTag[] IFD_GPS_TAGS = new ExifTag[] {
1207             new ExifTag(TAG_GPS_VERSION_ID, 0, IFD_FORMAT_BYTE),
1208             new ExifTag(TAG_GPS_LATITUDE_REF, 1, IFD_FORMAT_STRING),
1209             new ExifTag(TAG_GPS_LATITUDE, 2, IFD_FORMAT_URATIONAL),
1210             new ExifTag(TAG_GPS_LONGITUDE_REF, 3, IFD_FORMAT_STRING),
1211             new ExifTag(TAG_GPS_LONGITUDE, 4, IFD_FORMAT_URATIONAL),
1212             new ExifTag(TAG_GPS_ALTITUDE_REF, 5, IFD_FORMAT_BYTE),
1213             new ExifTag(TAG_GPS_ALTITUDE, 6, IFD_FORMAT_URATIONAL),
1214             new ExifTag(TAG_GPS_TIMESTAMP, 7, IFD_FORMAT_URATIONAL),
1215             new ExifTag(TAG_GPS_SATELLITES, 8, IFD_FORMAT_STRING),
1216             new ExifTag(TAG_GPS_STATUS, 9, IFD_FORMAT_STRING),
1217             new ExifTag(TAG_GPS_MEASURE_MODE, 10, IFD_FORMAT_STRING),
1218             new ExifTag(TAG_GPS_DOP, 11, IFD_FORMAT_URATIONAL),
1219             new ExifTag(TAG_GPS_SPEED_REF, 12, IFD_FORMAT_STRING),
1220             new ExifTag(TAG_GPS_SPEED, 13, IFD_FORMAT_URATIONAL),
1221             new ExifTag(TAG_GPS_TRACK_REF, 14, IFD_FORMAT_STRING),
1222             new ExifTag(TAG_GPS_TRACK, 15, IFD_FORMAT_URATIONAL),
1223             new ExifTag(TAG_GPS_IMG_DIRECTION_REF, 16, IFD_FORMAT_STRING),
1224             new ExifTag(TAG_GPS_IMG_DIRECTION, 17, IFD_FORMAT_URATIONAL),
1225             new ExifTag(TAG_GPS_MAP_DATUM, 18, IFD_FORMAT_STRING),
1226             new ExifTag(TAG_GPS_DEST_LATITUDE_REF, 19, IFD_FORMAT_STRING),
1227             new ExifTag(TAG_GPS_DEST_LATITUDE, 20, IFD_FORMAT_URATIONAL),
1228             new ExifTag(TAG_GPS_DEST_LONGITUDE_REF, 21, IFD_FORMAT_STRING),
1229             new ExifTag(TAG_GPS_DEST_LONGITUDE, 22, IFD_FORMAT_URATIONAL),
1230             new ExifTag(TAG_GPS_DEST_BEARING_REF, 23, IFD_FORMAT_STRING),
1231             new ExifTag(TAG_GPS_DEST_BEARING, 24, IFD_FORMAT_URATIONAL),
1232             new ExifTag(TAG_GPS_DEST_DISTANCE_REF, 25, IFD_FORMAT_STRING),
1233             new ExifTag(TAG_GPS_DEST_DISTANCE, 26, IFD_FORMAT_URATIONAL),
1234             new ExifTag(TAG_GPS_PROCESSING_METHOD, 27, IFD_FORMAT_UNDEFINED),
1235             new ExifTag(TAG_GPS_AREA_INFORMATION, 28, IFD_FORMAT_UNDEFINED),
1236             new ExifTag(TAG_GPS_DATESTAMP, 29, IFD_FORMAT_STRING),
1237             new ExifTag(TAG_GPS_DIFFERENTIAL, 30, IFD_FORMAT_USHORT)
1238     };
1239     // Primary image IFD Interoperability tag (See JEITA CP-3451C Section 4.6.8 Tag Support Levels)
1240     private static final ExifTag[] IFD_INTEROPERABILITY_TAGS = new ExifTag[] {
1241             new ExifTag(TAG_INTEROPERABILITY_INDEX, 1, IFD_FORMAT_STRING)
1242     };
1243     // IFD Thumbnail tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels)
1244     private static final ExifTag[] IFD_THUMBNAIL_TAGS = new ExifTag[] {
1245             // For below two, see TIFF 6.0 Spec Section 3: Bilevel Images.
1246             new ExifTag(TAG_NEW_SUBFILE_TYPE, 254, IFD_FORMAT_ULONG),
1247             new ExifTag(TAG_SUBFILE_TYPE, 255, IFD_FORMAT_ULONG),
1248             new ExifTag(TAG_THUMBNAIL_IMAGE_WIDTH, 256, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
1249             new ExifTag(TAG_THUMBNAIL_IMAGE_LENGTH, 257, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
1250             new ExifTag(TAG_BITS_PER_SAMPLE, 258, IFD_FORMAT_USHORT),
1251             new ExifTag(TAG_COMPRESSION, 259, IFD_FORMAT_USHORT),
1252             new ExifTag(TAG_PHOTOMETRIC_INTERPRETATION, 262, IFD_FORMAT_USHORT),
1253             new ExifTag(TAG_IMAGE_DESCRIPTION, 270, IFD_FORMAT_STRING),
1254             new ExifTag(TAG_MAKE, 271, IFD_FORMAT_STRING),
1255             new ExifTag(TAG_MODEL, 272, IFD_FORMAT_STRING),
1256             new ExifTag(TAG_STRIP_OFFSETS, 273, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
1257             new ExifTag(TAG_THUMBNAIL_ORIENTATION, 274, IFD_FORMAT_USHORT),
1258             new ExifTag(TAG_SAMPLES_PER_PIXEL, 277, IFD_FORMAT_USHORT),
1259             new ExifTag(TAG_ROWS_PER_STRIP, 278, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
1260             new ExifTag(TAG_STRIP_BYTE_COUNTS, 279, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
1261             new ExifTag(TAG_X_RESOLUTION, 282, IFD_FORMAT_URATIONAL),
1262             new ExifTag(TAG_Y_RESOLUTION, 283, IFD_FORMAT_URATIONAL),
1263             new ExifTag(TAG_PLANAR_CONFIGURATION, 284, IFD_FORMAT_USHORT),
1264             new ExifTag(TAG_RESOLUTION_UNIT, 296, IFD_FORMAT_USHORT),
1265             new ExifTag(TAG_TRANSFER_FUNCTION, 301, IFD_FORMAT_USHORT),
1266             new ExifTag(TAG_SOFTWARE, 305, IFD_FORMAT_STRING),
1267             new ExifTag(TAG_DATETIME, 306, IFD_FORMAT_STRING),
1268             new ExifTag(TAG_ARTIST, 315, IFD_FORMAT_STRING),
1269             new ExifTag(TAG_WHITE_POINT, 318, IFD_FORMAT_URATIONAL),
1270             new ExifTag(TAG_PRIMARY_CHROMATICITIES, 319, IFD_FORMAT_URATIONAL),
1271             // See Adobe PageMaker® 6.0 TIFF Technical Notes, Note 1.
1272             new ExifTag(TAG_SUB_IFD_POINTER, 330, IFD_FORMAT_ULONG),
1273             new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT, 513, IFD_FORMAT_ULONG),
1274             new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, 514, IFD_FORMAT_ULONG),
1275             new ExifTag(TAG_Y_CB_CR_COEFFICIENTS, 529, IFD_FORMAT_URATIONAL),
1276             new ExifTag(TAG_Y_CB_CR_SUB_SAMPLING, 530, IFD_FORMAT_USHORT),
1277             new ExifTag(TAG_Y_CB_CR_POSITIONING, 531, IFD_FORMAT_USHORT),
1278             new ExifTag(TAG_REFERENCE_BLACK_WHITE, 532, IFD_FORMAT_URATIONAL),
1279             new ExifTag(TAG_COPYRIGHT, 33432, IFD_FORMAT_STRING),
1280             new ExifTag(TAG_EXIF_IFD_POINTER, 34665, IFD_FORMAT_ULONG),
1281             new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG),
1282             new ExifTag(TAG_DNG_VERSION, 50706, IFD_FORMAT_BYTE),
1283             new ExifTag(TAG_DEFAULT_CROP_SIZE, 50720, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG)
1284     };
1285 
1286     // RAF file tag (See piex.cc line 372)
1287     private static final ExifTag TAG_RAF_IMAGE_SIZE =
1288             new ExifTag(TAG_STRIP_OFFSETS, 273, IFD_FORMAT_USHORT);
1289 
1290     // ORF file tags (See http://www.exiv2.org/tags-olympus.html)
1291     private static final ExifTag[] ORF_MAKER_NOTE_TAGS = new ExifTag[] {
1292             new ExifTag(TAG_ORF_THUMBNAIL_IMAGE, 256, IFD_FORMAT_UNDEFINED),
1293             new ExifTag(TAG_ORF_CAMERA_SETTINGS_IFD_POINTER, 8224, IFD_FORMAT_ULONG),
1294             new ExifTag(TAG_ORF_IMAGE_PROCESSING_IFD_POINTER, 8256, IFD_FORMAT_ULONG)
1295     };
1296     private static final ExifTag[] ORF_CAMERA_SETTINGS_TAGS = new ExifTag[] {
1297             new ExifTag(TAG_ORF_PREVIEW_IMAGE_START, 257, IFD_FORMAT_ULONG),
1298             new ExifTag(TAG_ORF_PREVIEW_IMAGE_LENGTH, 258, IFD_FORMAT_ULONG)
1299     };
1300     private static final ExifTag[] ORF_IMAGE_PROCESSING_TAGS = new ExifTag[] {
1301             new ExifTag(TAG_ORF_ASPECT_FRAME, 4371, IFD_FORMAT_USHORT)
1302     };
1303     // PEF file tag (See http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Pentax.html)
1304     private static final ExifTag[] PEF_TAGS = new ExifTag[] {
1305             new ExifTag(TAG_COLOR_SPACE, 55, IFD_FORMAT_USHORT)
1306     };
1307 
1308     // See JEITA CP-3451C Section 4.6.3: Exif-specific IFD.
1309     // The following values are used for indicating pointers to the other Image File Directories.
1310 
1311     // Indices of Exif Ifd tag groups
1312     /** @hide */
1313     @Retention(RetentionPolicy.SOURCE)
1314     @IntDef({IFD_TYPE_PRIMARY, IFD_TYPE_EXIF, IFD_TYPE_GPS, IFD_TYPE_INTEROPERABILITY,
1315             IFD_TYPE_THUMBNAIL, IFD_TYPE_PREVIEW, IFD_TYPE_ORF_MAKER_NOTE,
1316             IFD_TYPE_ORF_CAMERA_SETTINGS, IFD_TYPE_ORF_IMAGE_PROCESSING, IFD_TYPE_PEF})
1317     public @interface IfdType {}
1318 
1319     private static final int IFD_TYPE_PRIMARY = 0;
1320     private static final int IFD_TYPE_EXIF = 1;
1321     private static final int IFD_TYPE_GPS = 2;
1322     private static final int IFD_TYPE_INTEROPERABILITY = 3;
1323     private static final int IFD_TYPE_THUMBNAIL = 4;
1324     private static final int IFD_TYPE_PREVIEW = 5;
1325     private static final int IFD_TYPE_ORF_MAKER_NOTE = 6;
1326     private static final int IFD_TYPE_ORF_CAMERA_SETTINGS = 7;
1327     private static final int IFD_TYPE_ORF_IMAGE_PROCESSING = 8;
1328     private static final int IFD_TYPE_PEF = 9;
1329 
1330     // List of Exif tag groups
1331     private static final ExifTag[][] EXIF_TAGS = new ExifTag[][] {
1332             IFD_TIFF_TAGS, IFD_EXIF_TAGS, IFD_GPS_TAGS, IFD_INTEROPERABILITY_TAGS,
1333             IFD_THUMBNAIL_TAGS, IFD_TIFF_TAGS, ORF_MAKER_NOTE_TAGS, ORF_CAMERA_SETTINGS_TAGS,
1334             ORF_IMAGE_PROCESSING_TAGS, PEF_TAGS
1335     };
1336     // List of tags for pointing to the other image file directory offset.
1337     private static final ExifTag[] EXIF_POINTER_TAGS = new ExifTag[] {
1338             new ExifTag(TAG_SUB_IFD_POINTER, 330, IFD_FORMAT_ULONG),
1339             new ExifTag(TAG_EXIF_IFD_POINTER, 34665, IFD_FORMAT_ULONG),
1340             new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG),
1341             new ExifTag(TAG_INTEROPERABILITY_IFD_POINTER, 40965, IFD_FORMAT_ULONG),
1342             new ExifTag(TAG_ORF_CAMERA_SETTINGS_IFD_POINTER, 8224, IFD_FORMAT_BYTE),
1343             new ExifTag(TAG_ORF_IMAGE_PROCESSING_IFD_POINTER, 8256, IFD_FORMAT_BYTE)
1344     };
1345 
1346     // Tags for indicating the thumbnail offset and length
1347     private static final ExifTag JPEG_INTERCHANGE_FORMAT_TAG =
1348             new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT, 513, IFD_FORMAT_ULONG);
1349     private static final ExifTag JPEG_INTERCHANGE_FORMAT_LENGTH_TAG =
1350             new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, 514, IFD_FORMAT_ULONG);
1351 
1352     // Mappings from tag number to tag name and each item represents one IFD tag group.
1353     private static final HashMap[] sExifTagMapsForReading = new HashMap[EXIF_TAGS.length];
1354     // Mappings from tag name to tag number and each item represents one IFD tag group.
1355     private static final HashMap[] sExifTagMapsForWriting = new HashMap[EXIF_TAGS.length];
1356     private static final HashSet<String> sTagSetForCompatibility = new HashSet<>(Arrays.asList(
1357             TAG_F_NUMBER, TAG_DIGITAL_ZOOM_RATIO, TAG_EXPOSURE_TIME, TAG_SUBJECT_DISTANCE,
1358             TAG_GPS_TIMESTAMP));
1359     // Mappings from tag number to IFD type for pointer tags.
1360     private static final HashMap<Integer, Integer> sExifPointerTagMap = new HashMap();
1361 
1362     // See JPEG File Interchange Format Version 1.02.
1363     // The following values are defined for handling JPEG streams. In this implementation, we are
1364     // not only getting information from EXIF but also from some JPEG special segments such as
1365     // MARKER_COM for user comment and MARKER_SOFx for image width and height.
1366 
1367     private static final Charset ASCII = Charset.forName("US-ASCII");
1368     // Identifier for EXIF APP1 segment in JPEG
1369     private static final byte[] IDENTIFIER_EXIF_APP1 = "Exif\0\0".getBytes(ASCII);
1370     // Identifier for XMP APP1 segment in JPEG
1371     private static final byte[] IDENTIFIER_XMP_APP1 = "http://ns.adobe.com/xap/1.0/\0".getBytes(ASCII);
1372     // JPEG segment markers, that each marker consumes two bytes beginning with 0xff and ending with
1373     // the indicator. There is no SOF4, SOF8, SOF16 markers in JPEG and SOFx markers indicates start
1374     // of frame(baseline DCT) and the image size info exists in its beginning part.
1375     private static final byte MARKER = (byte) 0xff;
1376     private static final byte MARKER_SOI = (byte) 0xd8;
1377     private static final byte MARKER_SOF0 = (byte) 0xc0;
1378     private static final byte MARKER_SOF1 = (byte) 0xc1;
1379     private static final byte MARKER_SOF2 = (byte) 0xc2;
1380     private static final byte MARKER_SOF3 = (byte) 0xc3;
1381     private static final byte MARKER_SOF5 = (byte) 0xc5;
1382     private static final byte MARKER_SOF6 = (byte) 0xc6;
1383     private static final byte MARKER_SOF7 = (byte) 0xc7;
1384     private static final byte MARKER_SOF9 = (byte) 0xc9;
1385     private static final byte MARKER_SOF10 = (byte) 0xca;
1386     private static final byte MARKER_SOF11 = (byte) 0xcb;
1387     private static final byte MARKER_SOF13 = (byte) 0xcd;
1388     private static final byte MARKER_SOF14 = (byte) 0xce;
1389     private static final byte MARKER_SOF15 = (byte) 0xcf;
1390     private static final byte MARKER_SOS = (byte) 0xda;
1391     private static final byte MARKER_APP1 = (byte) 0xe1;
1392     private static final byte MARKER_COM = (byte) 0xfe;
1393     private static final byte MARKER_EOI = (byte) 0xd9;
1394 
1395     // Supported Image File Types
1396     private static final int IMAGE_TYPE_UNKNOWN = 0;
1397     private static final int IMAGE_TYPE_ARW = 1;
1398     private static final int IMAGE_TYPE_CR2 = 2;
1399     private static final int IMAGE_TYPE_DNG = 3;
1400     private static final int IMAGE_TYPE_JPEG = 4;
1401     private static final int IMAGE_TYPE_NEF = 5;
1402     private static final int IMAGE_TYPE_NRW = 6;
1403     private static final int IMAGE_TYPE_ORF = 7;
1404     private static final int IMAGE_TYPE_PEF = 8;
1405     private static final int IMAGE_TYPE_RAF = 9;
1406     private static final int IMAGE_TYPE_RW2 = 10;
1407     private static final int IMAGE_TYPE_SRW = 11;
1408     private static final int IMAGE_TYPE_HEIF = 12;
1409     private static final int IMAGE_TYPE_PNG = 13;
1410     private static final int IMAGE_TYPE_WEBP = 14;
1411 
1412     static {
1413         sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
1414         sFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
1415         sFormatterTz = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss XXX");
1416         sFormatterTz.setTimeZone(TimeZone.getTimeZone("UTC"));
1417 
1418         // Build up the hash tables to look up Exif tags for reading Exif tags.
1419         for (int ifdType = 0; ifdType < EXIF_TAGS.length; ++ifdType) {
1420             sExifTagMapsForReading[ifdType] = new HashMap();
1421             sExifTagMapsForWriting[ifdType] = new HashMap();
1422             for (ExifTag tag : EXIF_TAGS[ifdType]) {
put(tag.number, tag)1423                 sExifTagMapsForReading[ifdType].put(tag.number, tag);
put(tag.name, tag)1424                 sExifTagMapsForWriting[ifdType].put(tag.name, tag);
1425             }
1426         }
1427 
1428         // Build up the hash table to look up Exif pointer tags.
sExifPointerTagMap.put(EXIF_POINTER_TAGS[0].number, IFD_TYPE_PREVIEW)1429         sExifPointerTagMap.put(EXIF_POINTER_TAGS[0].number, IFD_TYPE_PREVIEW); // 330
sExifPointerTagMap.put(EXIF_POINTER_TAGS[1].number, IFD_TYPE_EXIF)1430         sExifPointerTagMap.put(EXIF_POINTER_TAGS[1].number, IFD_TYPE_EXIF); // 34665
sExifPointerTagMap.put(EXIF_POINTER_TAGS[2].number, IFD_TYPE_GPS)1431         sExifPointerTagMap.put(EXIF_POINTER_TAGS[2].number, IFD_TYPE_GPS); // 34853
sExifPointerTagMap.put(EXIF_POINTER_TAGS[3].number, IFD_TYPE_INTEROPERABILITY)1432         sExifPointerTagMap.put(EXIF_POINTER_TAGS[3].number, IFD_TYPE_INTEROPERABILITY); // 40965
sExifPointerTagMap.put(EXIF_POINTER_TAGS[4].number, IFD_TYPE_ORF_CAMERA_SETTINGS)1433         sExifPointerTagMap.put(EXIF_POINTER_TAGS[4].number, IFD_TYPE_ORF_CAMERA_SETTINGS); // 8224
sExifPointerTagMap.put(EXIF_POINTER_TAGS[5].number, IFD_TYPE_ORF_IMAGE_PROCESSING)1434         sExifPointerTagMap.put(EXIF_POINTER_TAGS[5].number, IFD_TYPE_ORF_IMAGE_PROCESSING); // 8256
1435     }
1436 
1437     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
1438     private String mFilename;
1439     private FileDescriptor mSeekableFileDescriptor;
1440     private AssetManager.AssetInputStream mAssetInputStream;
1441     private boolean mIsInputStream;
1442     private int mMimeType;
1443     private boolean mIsExifDataOnly;
1444     @UnsupportedAppUsage
1445     private final HashMap[] mAttributes = new HashMap[EXIF_TAGS.length];
1446     private Set<Integer> mHandledIfdOffsets = new HashSet<>(EXIF_TAGS.length);
1447     private ByteOrder mExifByteOrder = ByteOrder.BIG_ENDIAN;
1448     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
1449     private boolean mHasThumbnail;
1450     private boolean mHasThumbnailStrips;
1451     private boolean mAreThumbnailStripsConsecutive;
1452     // Used to indicate the position of the thumbnail (includes offset to EXIF data segment).
1453     private int mThumbnailOffset;
1454     private int mThumbnailLength;
1455     private byte[] mThumbnailBytes;
1456     private int mThumbnailCompression;
1457     private int mExifOffset;
1458     private int mOrfMakerNoteOffset;
1459     private int mOrfThumbnailOffset;
1460     private int mOrfThumbnailLength;
1461     private int mRw2JpgFromRawOffset;
1462     private boolean mIsSupportedFile;
1463     private boolean mModified;
1464     // XMP data can be contained as either part of the EXIF data (tag number 700), or as a
1465     // separate data marker (a separate MARKER_APP1).
1466     private boolean mXmpIsFromSeparateMarker;
1467 
1468     // Pattern to check non zero timestamp
1469     private static final Pattern sNonZeroTimePattern = Pattern.compile(".*[1-9].*");
1470     // Pattern to check gps timestamp
1471     private static final Pattern sGpsTimestampPattern =
1472             Pattern.compile("^([0-9][0-9]):([0-9][0-9]):([0-9][0-9])$");
1473 
1474     /**
1475      * Reads Exif tags from the specified image file.
1476      *
1477      * @param file the file of the image data
1478      * @throws NullPointerException if file is null
1479      * @throws IOException if an I/O error occurs while retrieving file descriptor via
1480      *         {@link FileInputStream#getFD()}.
1481      */
ExifInterface(@onNull File file)1482     public ExifInterface(@NonNull File file) throws IOException {
1483         if (file == null) {
1484             throw new NullPointerException("file cannot be null");
1485         }
1486         initForFilename(file.getAbsolutePath());
1487     }
1488 
1489     /**
1490      * Reads Exif tags from the specified image file.
1491      *
1492      * @param filename the name of the file of the image data
1493      * @throws NullPointerException if file name is null
1494      * @throws IOException if an I/O error occurs while retrieving file descriptor via
1495      *         {@link FileInputStream#getFD()}.
1496      */
ExifInterface(@onNull String filename)1497     public ExifInterface(@NonNull String filename) throws IOException {
1498         if (filename == null) {
1499             throw new NullPointerException("filename cannot be null");
1500         }
1501         initForFilename(filename);
1502     }
1503 
1504     /**
1505      * Reads Exif tags from the specified image file descriptor. Attribute mutation is supported
1506      * for writable and seekable file descriptors only. This constructor will not rewind the offset
1507      * of the given file descriptor. Developers should close the file descriptor after use.
1508      *
1509      * @param fileDescriptor the file descriptor of the image data
1510      * @throws NullPointerException if file descriptor is null
1511      * @throws IOException if an error occurs while duplicating the file descriptor via
1512      *         {@link Os#dup(FileDescriptor)}.
1513      */
ExifInterface(@onNull FileDescriptor fileDescriptor)1514     public ExifInterface(@NonNull FileDescriptor fileDescriptor) throws IOException {
1515         if (fileDescriptor == null) {
1516             throw new NullPointerException("fileDescriptor cannot be null");
1517         }
1518 
1519         mAssetInputStream = null;
1520         mFilename = null;
1521         // When FileDescriptor is duplicated and set to FileInputStream, ownership needs to be
1522         // clarified in order for garbage collection to take place.
1523         boolean isFdOwner = false;
1524         if (isSeekableFD(fileDescriptor)) {
1525             mSeekableFileDescriptor = fileDescriptor;
1526             // Keep the original file descriptor in order to save attributes when it's seekable.
1527             // Otherwise, just close the given file descriptor after reading it because the save
1528             // feature won't be working.
1529             try {
1530                 fileDescriptor = Os.dup(fileDescriptor);
1531                 isFdOwner = true;
1532             } catch (ErrnoException e) {
1533                 throw e.rethrowAsIOException();
1534             }
1535         } else {
1536             mSeekableFileDescriptor = null;
1537         }
1538         mIsInputStream = false;
1539         FileInputStream in = null;
1540         try {
1541             in = new FileInputStream(fileDescriptor, isFdOwner);
1542             loadAttributes(in);
1543         } finally {
1544             IoUtils.closeQuietly(in);
1545         }
1546     }
1547 
1548     /**
1549      * Reads Exif tags from the specified image input stream. Attribute mutation is not supported
1550      * for input streams. The given input stream will proceed from its current position. Developers
1551      * should close the input stream after use. This constructor is not intended to be used with an
1552      * input stream that performs any networking operations.
1553      *
1554      * @param inputStream the input stream that contains the image data
1555      * @throws NullPointerException if the input stream is null
1556      */
ExifInterface(@onNull InputStream inputStream)1557     public ExifInterface(@NonNull InputStream inputStream) throws IOException {
1558         this(inputStream, false);
1559     }
1560 
1561     /**
1562      * Reads Exif tags from the specified image input stream based on the stream type. Attribute
1563      * mutation is not supported for input streams. The given input stream will proceed from its
1564      * current position. Developers should close the input stream after use. This constructor is not
1565      * intended to be used with an input stream that performs any networking operations.
1566      *
1567      * @param inputStream the input stream that contains the image data
1568      * @param streamType the type of input stream
1569      * @throws NullPointerException if the input stream is null
1570      * @throws IOException if an I/O error occurs while retrieving file descriptor via
1571      *         {@link FileInputStream#getFD()}.
1572      */
ExifInterface(@onNull InputStream inputStream, @ExifStreamType int streamType)1573     public ExifInterface(@NonNull InputStream inputStream, @ExifStreamType int streamType)
1574             throws IOException {
1575         this(inputStream, (streamType == STREAM_TYPE_EXIF_DATA_ONLY) ? true : false);
1576     }
1577 
ExifInterface(@onNull InputStream inputStream, boolean shouldBeExifDataOnly)1578     private ExifInterface(@NonNull InputStream inputStream, boolean shouldBeExifDataOnly)
1579             throws IOException {
1580         if (inputStream == null) {
1581             throw new NullPointerException("inputStream cannot be null");
1582         }
1583         mFilename = null;
1584 
1585         if (shouldBeExifDataOnly) {
1586             inputStream = new BufferedInputStream(inputStream, SIGNATURE_CHECK_SIZE);
1587             if (!isExifDataOnly((BufferedInputStream) inputStream)) {
1588                 Log.w(TAG, "Given data does not follow the structure of an Exif-only data.");
1589                 return;
1590             }
1591             mIsExifDataOnly = true;
1592             mAssetInputStream = null;
1593             mSeekableFileDescriptor = null;
1594         } else {
1595             if (inputStream instanceof AssetManager.AssetInputStream) {
1596                 mAssetInputStream = (AssetManager.AssetInputStream) inputStream;
1597                 mSeekableFileDescriptor = null;
1598             } else if (inputStream instanceof FileInputStream
1599                     && (isSeekableFD(((FileInputStream) inputStream).getFD()))) {
1600                 mAssetInputStream = null;
1601                 mSeekableFileDescriptor = ((FileInputStream) inputStream).getFD();
1602             } else {
1603                 mAssetInputStream = null;
1604                 mSeekableFileDescriptor = null;
1605             }
1606         }
1607         loadAttributes(inputStream);
1608     }
1609 
1610     /**
1611      * Returns whether ExifInterface currently supports parsing data from the specified mime type
1612      * or not.
1613      *
1614      * @param mimeType the string value of mime type
1615      */
isSupportedMimeType(@onNull String mimeType)1616     public static boolean isSupportedMimeType(@NonNull String mimeType) {
1617         if (mimeType == null) {
1618             throw new NullPointerException("mimeType shouldn't be null");
1619         }
1620 
1621         switch (mimeType.toLowerCase(Locale.ROOT)) {
1622             case "image/jpeg":
1623             case "image/x-adobe-dng":
1624             case "image/x-canon-cr2":
1625             case "image/x-nikon-nef":
1626             case "image/x-nikon-nrw":
1627             case "image/x-sony-arw":
1628             case "image/x-panasonic-rw2":
1629             case "image/x-olympus-orf":
1630             case "image/x-pentax-pef":
1631             case "image/x-samsung-srw":
1632             case "image/x-fuji-raf":
1633             case "image/heic":
1634             case "image/heif":
1635                 return true;
1636             default:
1637                 return false;
1638         }
1639     }
1640 
1641     /**
1642      * Returns the EXIF attribute of the specified tag or {@code null} if there is no such tag in
1643      * the image file.
1644      *
1645      * @param tag the name of the tag.
1646      */
getExifAttribute(@onNull String tag)1647     private @Nullable ExifAttribute getExifAttribute(@NonNull String tag) {
1648         if (tag == null) {
1649             throw new NullPointerException("tag shouldn't be null");
1650         }
1651         // Retrieves all tag groups. The value from primary image tag group has a higher priority
1652         // than the value from the thumbnail tag group if there are more than one candidates.
1653         for (int i = 0; i < EXIF_TAGS.length; ++i) {
1654             Object value = mAttributes[i].get(tag);
1655             if (value != null) {
1656                 return (ExifAttribute) value;
1657             }
1658         }
1659         return null;
1660     }
1661 
1662     /**
1663      * Returns the value of the specified tag or {@code null} if there
1664      * is no such tag in the image file.
1665      *
1666      * @param tag the name of the tag.
1667      */
getAttribute(@onNull String tag)1668     public @Nullable String getAttribute(@NonNull String tag) {
1669         if (tag == null) {
1670             throw new NullPointerException("tag shouldn't be null");
1671         }
1672         ExifAttribute attribute = getExifAttribute(tag);
1673         if (attribute != null) {
1674             if (!sTagSetForCompatibility.contains(tag)) {
1675                 return attribute.getStringValue(mExifByteOrder);
1676             }
1677             if (tag.equals(TAG_GPS_TIMESTAMP)) {
1678                 // Convert the rational values to the custom formats for backwards compatibility.
1679                 if (attribute.format != IFD_FORMAT_URATIONAL
1680                         && attribute.format != IFD_FORMAT_SRATIONAL) {
1681                     return null;
1682                 }
1683                 Rational[] array = (Rational[]) attribute.getValue(mExifByteOrder);
1684                 if (array.length != 3) {
1685                     return null;
1686                 }
1687                 return String.format("%02d:%02d:%02d",
1688                         (int) ((float) array[0].numerator / array[0].denominator),
1689                         (int) ((float) array[1].numerator / array[1].denominator),
1690                         (int) ((float) array[2].numerator / array[2].denominator));
1691             }
1692             try {
1693                 return Double.toString(attribute.getDoubleValue(mExifByteOrder));
1694             } catch (NumberFormatException e) {
1695                 return null;
1696             }
1697         }
1698         return null;
1699     }
1700 
1701     /**
1702      * Returns the integer value of the specified tag. If there is no such tag
1703      * in the image file or the value cannot be parsed as integer, return
1704      * <var>defaultValue</var>.
1705      *
1706      * @param tag the name of the tag.
1707      * @param defaultValue the value to return if the tag is not available.
1708      */
getAttributeInt(@onNull String tag, int defaultValue)1709     public int getAttributeInt(@NonNull String tag, int defaultValue) {
1710         if (tag == null) {
1711             throw new NullPointerException("tag shouldn't be null");
1712         }
1713         ExifAttribute exifAttribute = getExifAttribute(tag);
1714         if (exifAttribute == null) {
1715             return defaultValue;
1716         }
1717 
1718         try {
1719             return exifAttribute.getIntValue(mExifByteOrder);
1720         } catch (NumberFormatException e) {
1721             return defaultValue;
1722         }
1723     }
1724 
1725     /**
1726      * Returns the double value of the tag that is specified as rational or contains a
1727      * double-formatted value. If there is no such tag in the image file or the value cannot be
1728      * parsed as double, return <var>defaultValue</var>.
1729      *
1730      * @param tag the name of the tag.
1731      * @param defaultValue the value to return if the tag is not available.
1732      */
getAttributeDouble(@onNull String tag, double defaultValue)1733     public double getAttributeDouble(@NonNull String tag, double defaultValue) {
1734         if (tag == null) {
1735             throw new NullPointerException("tag shouldn't be null");
1736         }
1737         ExifAttribute exifAttribute = getExifAttribute(tag);
1738         if (exifAttribute == null) {
1739             return defaultValue;
1740         }
1741 
1742         try {
1743             return exifAttribute.getDoubleValue(mExifByteOrder);
1744         } catch (NumberFormatException e) {
1745             return defaultValue;
1746         }
1747     }
1748 
1749     /**
1750      * Set the value of the specified tag.
1751      *
1752      * @param tag the name of the tag.
1753      * @param value the value of the tag.
1754      */
setAttribute(@onNull String tag, @Nullable String value)1755     public void setAttribute(@NonNull String tag, @Nullable String value) {
1756         if (tag == null) {
1757             throw new NullPointerException("tag shouldn't be null");
1758         }
1759         // Convert the given value to rational values for backwards compatibility.
1760         if (value != null && sTagSetForCompatibility.contains(tag)) {
1761             if (tag.equals(TAG_GPS_TIMESTAMP)) {
1762                 Matcher m = sGpsTimestampPattern.matcher(value);
1763                 if (!m.find()) {
1764                     Log.w(TAG, "Invalid value for " + tag + " : " + value);
1765                     return;
1766                 }
1767                 value = Integer.parseInt(m.group(1)) + "/1," + Integer.parseInt(m.group(2)) + "/1,"
1768                         + Integer.parseInt(m.group(3)) + "/1";
1769             } else {
1770                 try {
1771                     double doubleValue = Double.parseDouble(value);
1772                     value = (long) (doubleValue * 10000L) + "/10000";
1773                 } catch (NumberFormatException e) {
1774                     Log.w(TAG, "Invalid value for " + tag + " : " + value);
1775                     return;
1776                 }
1777             }
1778         }
1779 
1780         for (int i = 0 ; i < EXIF_TAGS.length; ++i) {
1781             if (i == IFD_TYPE_THUMBNAIL && !mHasThumbnail) {
1782                 continue;
1783             }
1784             final Object obj = sExifTagMapsForWriting[i].get(tag);
1785             if (obj != null) {
1786                 if (value == null) {
1787                     mAttributes[i].remove(tag);
1788                     continue;
1789                 }
1790                 final ExifTag exifTag = (ExifTag) obj;
1791                 Pair<Integer, Integer> guess = guessDataFormat(value);
1792                 int dataFormat;
1793                 if (exifTag.primaryFormat == guess.first || exifTag.primaryFormat == guess.second) {
1794                     dataFormat = exifTag.primaryFormat;
1795                 } else if (exifTag.secondaryFormat != -1 && (exifTag.secondaryFormat == guess.first
1796                         || exifTag.secondaryFormat == guess.second)) {
1797                     dataFormat = exifTag.secondaryFormat;
1798                 } else if (exifTag.primaryFormat == IFD_FORMAT_BYTE
1799                         || exifTag.primaryFormat == IFD_FORMAT_UNDEFINED
1800                         || exifTag.primaryFormat == IFD_FORMAT_STRING) {
1801                     dataFormat = exifTag.primaryFormat;
1802                 } else {
1803                     if (DEBUG) {
1804                         Log.d(TAG, "Given tag (" + tag
1805                                 + ") value didn't match with one of expected "
1806                                 + "formats: " + IFD_FORMAT_NAMES[exifTag.primaryFormat]
1807                                 + (exifTag.secondaryFormat == -1 ? "" : ", "
1808                                 + IFD_FORMAT_NAMES[exifTag.secondaryFormat]) + " (guess: "
1809                                 + IFD_FORMAT_NAMES[guess.first] + (guess.second == -1 ? "" : ", "
1810                                 + IFD_FORMAT_NAMES[guess.second]) + ")");
1811                     }
1812                     continue;
1813                 }
1814                 switch (dataFormat) {
1815                     case IFD_FORMAT_BYTE: {
1816                         mAttributes[i].put(tag, ExifAttribute.createByte(value));
1817                         break;
1818                     }
1819                     case IFD_FORMAT_UNDEFINED:
1820                     case IFD_FORMAT_STRING: {
1821                         mAttributes[i].put(tag, ExifAttribute.createString(value));
1822                         break;
1823                     }
1824                     case IFD_FORMAT_USHORT: {
1825                         final String[] values = value.split(",");
1826                         final int[] intArray = new int[values.length];
1827                         for (int j = 0; j < values.length; ++j) {
1828                             intArray[j] = Integer.parseInt(values[j]);
1829                         }
1830                         mAttributes[i].put(tag,
1831                                 ExifAttribute.createUShort(intArray, mExifByteOrder));
1832                         break;
1833                     }
1834                     case IFD_FORMAT_SLONG: {
1835                         final String[] values = value.split(",");
1836                         final int[] intArray = new int[values.length];
1837                         for (int j = 0; j < values.length; ++j) {
1838                             intArray[j] = Integer.parseInt(values[j]);
1839                         }
1840                         mAttributes[i].put(tag,
1841                                 ExifAttribute.createSLong(intArray, mExifByteOrder));
1842                         break;
1843                     }
1844                     case IFD_FORMAT_ULONG: {
1845                         final String[] values = value.split(",");
1846                         final long[] longArray = new long[values.length];
1847                         for (int j = 0; j < values.length; ++j) {
1848                             longArray[j] = Long.parseLong(values[j]);
1849                         }
1850                         mAttributes[i].put(tag,
1851                                 ExifAttribute.createULong(longArray, mExifByteOrder));
1852                         break;
1853                     }
1854                     case IFD_FORMAT_URATIONAL: {
1855                         final String[] values = value.split(",");
1856                         final Rational[] rationalArray = new Rational[values.length];
1857                         for (int j = 0; j < values.length; ++j) {
1858                             final String[] numbers = values[j].split("/");
1859                             rationalArray[j] = new Rational((long) Double.parseDouble(numbers[0]),
1860                                     (long) Double.parseDouble(numbers[1]));
1861                         }
1862                         mAttributes[i].put(tag,
1863                                 ExifAttribute.createURational(rationalArray, mExifByteOrder));
1864                         break;
1865                     }
1866                     case IFD_FORMAT_SRATIONAL: {
1867                         final String[] values = value.split(",");
1868                         final Rational[] rationalArray = new Rational[values.length];
1869                         for (int j = 0; j < values.length; ++j) {
1870                             final String[] numbers = values[j].split("/");
1871                             rationalArray[j] = new Rational((long) Double.parseDouble(numbers[0]),
1872                                     (long) Double.parseDouble(numbers[1]));
1873                         }
1874                         mAttributes[i].put(tag,
1875                                 ExifAttribute.createSRational(rationalArray, mExifByteOrder));
1876                         break;
1877                     }
1878                     case IFD_FORMAT_DOUBLE: {
1879                         final String[] values = value.split(",");
1880                         final double[] doubleArray = new double[values.length];
1881                         for (int j = 0; j < values.length; ++j) {
1882                             doubleArray[j] = Double.parseDouble(values[j]);
1883                         }
1884                         mAttributes[i].put(tag,
1885                                 ExifAttribute.createDouble(doubleArray, mExifByteOrder));
1886                         break;
1887                     }
1888                     default:
1889                         if (DEBUG) {
1890                             Log.d(TAG, "Data format isn't one of expected formats: " + dataFormat);
1891                         }
1892                         continue;
1893                 }
1894             }
1895         }
1896     }
1897 
1898     /**
1899      * Update the values of the tags in the tag groups if any value for the tag already was stored.
1900      *
1901      * @param tag the name of the tag.
1902      * @param value the value of the tag in a form of {@link ExifAttribute}.
1903      * @return Returns {@code true} if updating is placed.
1904      */
updateAttribute(String tag, ExifAttribute value)1905     private boolean updateAttribute(String tag, ExifAttribute value) {
1906         boolean updated = false;
1907         for (int i = 0 ; i < EXIF_TAGS.length; ++i) {
1908             if (mAttributes[i].containsKey(tag)) {
1909                 mAttributes[i].put(tag, value);
1910                 updated = true;
1911             }
1912         }
1913         return updated;
1914     }
1915 
1916     /**
1917      * Remove any values of the specified tag.
1918      *
1919      * @param tag the name of the tag.
1920      */
removeAttribute(String tag)1921     private void removeAttribute(String tag) {
1922         for (int i = 0 ; i < EXIF_TAGS.length; ++i) {
1923             mAttributes[i].remove(tag);
1924         }
1925     }
1926 
1927     /**
1928      * This function decides which parser to read the image data according to the given input stream
1929      * type and the content of the input stream.
1930      */
loadAttributes(@onNull InputStream in)1931     private void loadAttributes(@NonNull InputStream in) {
1932         if (in == null) {
1933             throw new NullPointerException("inputstream shouldn't be null");
1934         }
1935         try {
1936             // Initialize mAttributes.
1937             for (int i = 0; i < EXIF_TAGS.length; ++i) {
1938                 mAttributes[i] = new HashMap();
1939             }
1940 
1941             // Check file type
1942             if (!mIsExifDataOnly) {
1943                 in = new BufferedInputStream(in, SIGNATURE_CHECK_SIZE);
1944                 mMimeType = getMimeType((BufferedInputStream) in);
1945             }
1946 
1947             // Create byte-ordered input stream
1948             ByteOrderedDataInputStream inputStream = new ByteOrderedDataInputStream(in);
1949 
1950             if (!mIsExifDataOnly) {
1951                 switch (mMimeType) {
1952                     case IMAGE_TYPE_JPEG: {
1953                         getJpegAttributes(inputStream, 0, IFD_TYPE_PRIMARY); // 0 is offset
1954                         break;
1955                     }
1956                     case IMAGE_TYPE_RAF: {
1957                         getRafAttributes(inputStream);
1958                         break;
1959                     }
1960                     case IMAGE_TYPE_HEIF: {
1961                         getHeifAttributes(inputStream);
1962                         break;
1963                     }
1964                     case IMAGE_TYPE_ORF: {
1965                         getOrfAttributes(inputStream);
1966                         break;
1967                     }
1968                     case IMAGE_TYPE_RW2: {
1969                         getRw2Attributes(inputStream);
1970                         break;
1971                     }
1972                     case IMAGE_TYPE_PNG: {
1973                         getPngAttributes(inputStream);
1974                         break;
1975                     }
1976                     case IMAGE_TYPE_WEBP: {
1977                         getWebpAttributes(inputStream);
1978                         break;
1979                     }
1980                     case IMAGE_TYPE_ARW:
1981                     case IMAGE_TYPE_CR2:
1982                     case IMAGE_TYPE_DNG:
1983                     case IMAGE_TYPE_NEF:
1984                     case IMAGE_TYPE_NRW:
1985                     case IMAGE_TYPE_PEF:
1986                     case IMAGE_TYPE_SRW:
1987                     case IMAGE_TYPE_UNKNOWN: {
1988                         getRawAttributes(inputStream);
1989                         break;
1990                     }
1991                     default: {
1992                         break;
1993                     }
1994                 }
1995             } else {
1996                 getStandaloneAttributes(inputStream);
1997             }
1998             // Set thumbnail image offset and length
1999             setThumbnailData(inputStream);
2000             mIsSupportedFile = true;
2001         } catch (IOException | OutOfMemoryError e) {
2002             // Ignore exceptions in order to keep the compatibility with the old versions of
2003             // ExifInterface.
2004             mIsSupportedFile = false;
2005             Log.w(TAG, "Invalid image: ExifInterface got an unsupported image format file"
2006                     + "(ExifInterface supports JPEG and some RAW image formats only) "
2007                     + "or a corrupted JPEG file to ExifInterface.", e);
2008         } finally {
2009             addDefaultValuesForCompatibility();
2010 
2011             if (DEBUG) {
2012                 printAttributes();
2013             }
2014         }
2015     }
2016 
isSeekableFD(FileDescriptor fd)2017     private static boolean isSeekableFD(FileDescriptor fd) {
2018         try {
2019             Os.lseek(fd, 0, OsConstants.SEEK_CUR);
2020             return true;
2021         } catch (ErrnoException e) {
2022             if (DEBUG) {
2023                 Log.d(TAG, "The file descriptor for the given input is not seekable");
2024             }
2025             return false;
2026         }
2027     }
2028 
2029     // Prints out attributes for debugging.
printAttributes()2030     private void printAttributes() {
2031         for (int i = 0; i < mAttributes.length; ++i) {
2032             Log.d(TAG, "The size of tag group[" + i + "]: " + mAttributes[i].size());
2033             for (Map.Entry entry : (Set<Map.Entry>) mAttributes[i].entrySet()) {
2034                 final ExifAttribute tagValue = (ExifAttribute) entry.getValue();
2035                 Log.d(TAG, "tagName: " + entry.getKey() + ", tagType: " + tagValue.toString()
2036                         + ", tagValue: '" + tagValue.getStringValue(mExifByteOrder) + "'");
2037             }
2038         }
2039     }
2040 
2041     /**
2042      * Save the tag data into the original image file. This is expensive because
2043      * it involves copying all the data from one file to another and deleting
2044      * the old file and renaming the other. It's best to use
2045      * {@link #setAttribute(String,String)} to set all attributes to write and
2046      * make a single call rather than multiple calls for each attribute.
2047      * <p>
2048      * This method is only supported for JPEG and PNG files.
2049      * <p class="note">
2050      * Note: after calling this method, any attempts to obtain range information
2051      * from {@link #getAttributeRange(String)} or {@link #getThumbnailRange()}
2052      * will throw {@link IllegalStateException}, since the offsets may have
2053      * changed in the newly written file.
2054      * </p>
2055      */
saveAttributes()2056     public void saveAttributes() throws IOException {
2057         if (!mIsSupportedFile || (mMimeType != IMAGE_TYPE_JPEG && mMimeType != IMAGE_TYPE_PNG)) {
2058             throw new IOException("ExifInterface only supports saving attributes on JPEG or PNG "
2059                     + "formats.");
2060         }
2061         if (mIsInputStream || (mSeekableFileDescriptor == null && mFilename == null)) {
2062             throw new IOException(
2063                     "ExifInterface does not support saving attributes for the current input.");
2064         }
2065 
2066         // Remember the fact that we've changed the file on disk from what was
2067         // originally parsed, meaning we can't answer range questions
2068         mModified = true;
2069 
2070         // Keep the thumbnail in memory
2071         mThumbnailBytes = getThumbnail();
2072 
2073         FileInputStream in = null;
2074         FileOutputStream out = null;
2075         File originalFile = null;
2076         if (mFilename != null) {
2077             originalFile = new File(mFilename);
2078         }
2079         File tempFile = null;
2080         try {
2081             // Move the original file to temporary file.
2082             if (mFilename != null) {
2083                 String parent = originalFile.getParent();
2084                 String name = originalFile.getName();
2085                 String tempPrefix = UUID.randomUUID().toString() + "_";
2086                 tempFile = new File(parent, tempPrefix + name);
2087                 if (!originalFile.renameTo(tempFile)) {
2088                     throw new IOException("Couldn't rename to " + tempFile.getAbsolutePath());
2089                 }
2090             } else if (mSeekableFileDescriptor != null) {
2091                 if (mMimeType == IMAGE_TYPE_JPEG) {
2092                     tempFile = File.createTempFile("temp", "jpg");
2093                 } else if (mMimeType == IMAGE_TYPE_PNG) {
2094                     tempFile = File.createTempFile("temp", "png");
2095                 }
2096                 Os.lseek(mSeekableFileDescriptor, 0, OsConstants.SEEK_SET);
2097                 in = new FileInputStream(mSeekableFileDescriptor);
2098                 out = new FileOutputStream(tempFile);
2099                 Streams.copy(in, out);
2100             }
2101         } catch (Exception e) {
2102             throw new IOException("Failed to copy original file to temp file", e);
2103         } finally {
2104             IoUtils.closeQuietly(in);
2105             IoUtils.closeQuietly(out);
2106         }
2107 
2108         in = null;
2109         out = null;
2110         try {
2111             // Save the new file.
2112             in = new FileInputStream(tempFile);
2113             if (mFilename != null) {
2114                 out = new FileOutputStream(mFilename);
2115             } else if (mSeekableFileDescriptor != null) {
2116                 Os.lseek(mSeekableFileDescriptor, 0, OsConstants.SEEK_SET);
2117                 out = new FileOutputStream(mSeekableFileDescriptor);
2118             }
2119             try (BufferedInputStream bufferedIn = new BufferedInputStream(in);
2120                  BufferedOutputStream bufferedOut = new BufferedOutputStream(out)) {
2121                 if (mMimeType == IMAGE_TYPE_JPEG) {
2122                     saveJpegAttributes(bufferedIn, bufferedOut);
2123                 } else if (mMimeType == IMAGE_TYPE_PNG) {
2124                     savePngAttributes(bufferedIn, bufferedOut);
2125                 }
2126             }
2127         } catch (Exception e) {
2128             if (mFilename != null) {
2129                 if (!tempFile.renameTo(originalFile)) {
2130                     throw new IOException("Couldn't restore original file: "
2131                             + originalFile.getAbsolutePath());
2132                 }
2133             }
2134             throw new IOException("Failed to save new file", e);
2135         } finally {
2136             IoUtils.closeQuietly(in);
2137             IoUtils.closeQuietly(out);
2138             tempFile.delete();
2139         }
2140 
2141         // Discard the thumbnail in memory
2142         mThumbnailBytes = null;
2143     }
2144 
2145     /**
2146      * Returns true if the image file has a thumbnail.
2147      */
hasThumbnail()2148     public boolean hasThumbnail() {
2149         return mHasThumbnail;
2150     }
2151 
2152     /**
2153      * Returns true if the image file has the given attribute defined.
2154      *
2155      * @param tag the name of the tag.
2156      */
hasAttribute(@onNull String tag)2157     public boolean hasAttribute(@NonNull String tag) {
2158         return (getExifAttribute(tag) != null);
2159     }
2160 
2161     /**
2162      * Returns the JPEG compressed thumbnail inside the image file, or {@code null} if there is no
2163      * JPEG compressed thumbnail.
2164      * The returned data can be decoded using
2165      * {@link android.graphics.BitmapFactory#decodeByteArray(byte[],int,int)}
2166      */
getThumbnail()2167     public byte[] getThumbnail() {
2168         if (mThumbnailCompression == DATA_JPEG || mThumbnailCompression == DATA_JPEG_COMPRESSED) {
2169             return getThumbnailBytes();
2170         }
2171         return null;
2172     }
2173 
2174     /**
2175      * Returns the thumbnail bytes inside the image file, regardless of the compression type of the
2176      * thumbnail image.
2177      */
getThumbnailBytes()2178     public byte[] getThumbnailBytes() {
2179         if (!mHasThumbnail) {
2180             return null;
2181         }
2182         if (mThumbnailBytes != null) {
2183             return mThumbnailBytes;
2184         }
2185 
2186         // Read the thumbnail.
2187         InputStream in = null;
2188         try {
2189             if (mAssetInputStream != null) {
2190                 in = mAssetInputStream;
2191                 if (in.markSupported()) {
2192                     in.reset();
2193                 } else {
2194                     Log.d(TAG, "Cannot read thumbnail from inputstream without mark/reset support");
2195                     return null;
2196                 }
2197             } else if (mFilename != null) {
2198                 in = new FileInputStream(mFilename);
2199             } else if (mSeekableFileDescriptor != null) {
2200                 FileDescriptor fileDescriptor = Os.dup(mSeekableFileDescriptor);
2201                 Os.lseek(fileDescriptor, 0, OsConstants.SEEK_SET);
2202                 in = new FileInputStream(fileDescriptor, true);
2203             }
2204             if (in == null) {
2205                 // Should not be reached this.
2206                 throw new FileNotFoundException();
2207             }
2208             if (in.skip(mThumbnailOffset) != mThumbnailOffset) {
2209                 throw new IOException("Corrupted image");
2210             }
2211             // TODO: Need to handle potential OutOfMemoryError
2212             byte[] buffer = new byte[mThumbnailLength];
2213             if (in.read(buffer) != mThumbnailLength) {
2214                 throw new IOException("Corrupted image");
2215             }
2216             mThumbnailBytes = buffer;
2217             return buffer;
2218         } catch (IOException | ErrnoException e) {
2219             // Couldn't get a thumbnail image.
2220             Log.d(TAG, "Encountered exception while getting thumbnail", e);
2221         } finally {
2222             IoUtils.closeQuietly(in);
2223         }
2224         return null;
2225     }
2226 
2227     /**
2228      * Creates and returns a Bitmap object of the thumbnail image based on the byte array and the
2229      * thumbnail compression value, or {@code null} if the compression type is unsupported.
2230      */
getThumbnailBitmap()2231     public Bitmap getThumbnailBitmap() {
2232         if (!mHasThumbnail) {
2233             return null;
2234         } else if (mThumbnailBytes == null) {
2235             mThumbnailBytes = getThumbnailBytes();
2236         }
2237 
2238         if (mThumbnailCompression == DATA_JPEG || mThumbnailCompression == DATA_JPEG_COMPRESSED) {
2239             return BitmapFactory.decodeByteArray(mThumbnailBytes, 0, mThumbnailLength);
2240         } else if (mThumbnailCompression == DATA_UNCOMPRESSED) {
2241             int[] rgbValues = new int[mThumbnailBytes.length / 3];
2242             byte alpha = (byte) 0xff000000;
2243             for (int i = 0; i < rgbValues.length; i++) {
2244                 rgbValues[i] = alpha + (mThumbnailBytes[3 * i] << 16)
2245                         + (mThumbnailBytes[3 * i + 1] << 8) + mThumbnailBytes[3 * i + 2];
2246             }
2247 
2248             ExifAttribute imageLengthAttribute =
2249                     (ExifAttribute) mAttributes[IFD_TYPE_THUMBNAIL].get(TAG_IMAGE_LENGTH);
2250             ExifAttribute imageWidthAttribute =
2251                     (ExifAttribute) mAttributes[IFD_TYPE_THUMBNAIL].get(TAG_IMAGE_WIDTH);
2252             if (imageLengthAttribute != null && imageWidthAttribute != null) {
2253                 int imageLength = imageLengthAttribute.getIntValue(mExifByteOrder);
2254                 int imageWidth = imageWidthAttribute.getIntValue(mExifByteOrder);
2255                 return Bitmap.createBitmap(
2256                         rgbValues, imageWidth, imageLength, Bitmap.Config.ARGB_8888);
2257             }
2258         }
2259         return null;
2260     }
2261 
2262     /**
2263      * Returns true if thumbnail image is JPEG Compressed, or false if either thumbnail image does
2264      * not exist or thumbnail image is uncompressed.
2265      */
isThumbnailCompressed()2266     public boolean isThumbnailCompressed() {
2267         if (!mHasThumbnail) {
2268             return false;
2269         }
2270         if (mThumbnailCompression == DATA_JPEG || mThumbnailCompression == DATA_JPEG_COMPRESSED) {
2271             return true;
2272         }
2273         return false;
2274     }
2275 
2276     /**
2277      * Returns the offset and length of thumbnail inside the image file, or
2278      * {@code null} if either there is no thumbnail or the thumbnail bytes are stored
2279      * non-consecutively.
2280      *
2281      * @return two-element array, the offset in the first value, and length in
2282      *         the second, or {@code null} if no thumbnail was found or the thumbnail strips are
2283      *         not placed consecutively.
2284      * @throws IllegalStateException if {@link #saveAttributes()} has been
2285      *             called since the underlying file was initially parsed, since
2286      *             that means offsets may have changed.
2287      */
getThumbnailRange()2288     public @Nullable long[] getThumbnailRange() {
2289         if (mModified) {
2290             throw new IllegalStateException(
2291                     "The underlying file has been modified since being parsed");
2292         }
2293 
2294         if (mHasThumbnail) {
2295             if (mHasThumbnailStrips && !mAreThumbnailStripsConsecutive) {
2296                 return null;
2297             }
2298             return new long[] { mThumbnailOffset, mThumbnailLength };
2299         }
2300         return null;
2301     }
2302 
2303     /**
2304      * Returns the offset and length of the requested tag inside the image file,
2305      * or {@code null} if the tag is not contained.
2306      *
2307      * @return two-element array, the offset in the first value, and length in
2308      *         the second, or {@code null} if no tag was found.
2309      * @throws IllegalStateException if {@link #saveAttributes()} has been
2310      *             called since the underlying file was initially parsed, since
2311      *             that means offsets may have changed.
2312      */
getAttributeRange(@onNull String tag)2313     public @Nullable long[] getAttributeRange(@NonNull String tag) {
2314         if (tag == null) {
2315             throw new NullPointerException("tag shouldn't be null");
2316         }
2317         if (mModified) {
2318             throw new IllegalStateException(
2319                     "The underlying file has been modified since being parsed");
2320         }
2321 
2322         final ExifAttribute attribute = getExifAttribute(tag);
2323         if (attribute != null) {
2324             return new long[] { attribute.bytesOffset, attribute.bytes.length };
2325         } else {
2326             return null;
2327         }
2328     }
2329 
2330     /**
2331      * Returns the raw bytes for the value of the requested tag inside the image
2332      * file, or {@code null} if the tag is not contained.
2333      *
2334      * @return raw bytes for the value of the requested tag, or {@code null} if
2335      *         no tag was found.
2336      */
getAttributeBytes(@onNull String tag)2337     public @Nullable byte[] getAttributeBytes(@NonNull String tag) {
2338         if (tag == null) {
2339             throw new NullPointerException("tag shouldn't be null");
2340         }
2341         final ExifAttribute attribute = getExifAttribute(tag);
2342         if (attribute != null) {
2343             return attribute.bytes;
2344         } else {
2345             return null;
2346         }
2347     }
2348 
2349     /**
2350      * Stores the latitude and longitude value in a float array. The first element is
2351      * the latitude, and the second element is the longitude. Returns false if the
2352      * Exif tags are not available.
2353      */
getLatLong(float output[])2354     public boolean getLatLong(float output[]) {
2355         String latValue = getAttribute(TAG_GPS_LATITUDE);
2356         String latRef = getAttribute(TAG_GPS_LATITUDE_REF);
2357         String lngValue = getAttribute(TAG_GPS_LONGITUDE);
2358         String lngRef = getAttribute(TAG_GPS_LONGITUDE_REF);
2359 
2360         if (latValue != null && latRef != null && lngValue != null && lngRef != null) {
2361             try {
2362                 output[0] = convertRationalLatLonToFloat(latValue, latRef);
2363                 output[1] = convertRationalLatLonToFloat(lngValue, lngRef);
2364                 return true;
2365             } catch (IllegalArgumentException e) {
2366                 // if values are not parseable
2367             }
2368         }
2369 
2370         return false;
2371     }
2372 
2373     /**
2374      * Return the altitude in meters. If the exif tag does not exist, return
2375      * <var>defaultValue</var>.
2376      *
2377      * @param defaultValue the value to return if the tag is not available.
2378      */
getAltitude(double defaultValue)2379     public double getAltitude(double defaultValue) {
2380         double altitude = getAttributeDouble(TAG_GPS_ALTITUDE, -1);
2381         int ref = getAttributeInt(TAG_GPS_ALTITUDE_REF, -1);
2382 
2383         if (altitude >= 0 && ref >= 0) {
2384             return (altitude * ((ref == 1) ? -1 : 1));
2385         } else {
2386             return defaultValue;
2387         }
2388     }
2389 
2390     /**
2391      * Returns parsed {@code DateTime} value, or -1 if unavailable or invalid.
2392      *
2393      * @hide
2394      */
2395     @UnsupportedAppUsage
getDateTime()2396     public @CurrentTimeMillisLong long getDateTime() {
2397         return parseDateTime(getAttribute(TAG_DATETIME),
2398                 getAttribute(TAG_SUBSEC_TIME),
2399                 getAttribute(TAG_OFFSET_TIME));
2400     }
2401 
2402     /**
2403      * Returns parsed {@code DateTimeDigitized} value, or -1 if unavailable or
2404      * invalid.
2405      *
2406      * @hide
2407      */
getDateTimeDigitized()2408     public @CurrentTimeMillisLong long getDateTimeDigitized() {
2409         return parseDateTime(getAttribute(TAG_DATETIME_DIGITIZED),
2410                 getAttribute(TAG_SUBSEC_TIME_DIGITIZED),
2411                 getAttribute(TAG_OFFSET_TIME_DIGITIZED));
2412     }
2413 
2414     /**
2415      * Returns parsed {@code DateTimeOriginal} value, or -1 if unavailable or
2416      * invalid.
2417      *
2418      * @hide
2419      */
2420     @UnsupportedAppUsage
getDateTimeOriginal()2421     public @CurrentTimeMillisLong long getDateTimeOriginal() {
2422         return parseDateTime(getAttribute(TAG_DATETIME_ORIGINAL),
2423                 getAttribute(TAG_SUBSEC_TIME_ORIGINAL),
2424                 getAttribute(TAG_OFFSET_TIME_ORIGINAL));
2425     }
2426 
parseDateTime(@ullable String dateTimeString, @Nullable String subSecs, @Nullable String offsetString)2427     private static @CurrentTimeMillisLong long parseDateTime(@Nullable String dateTimeString,
2428             @Nullable String subSecs, @Nullable String offsetString) {
2429         if (dateTimeString == null
2430                 || !sNonZeroTimePattern.matcher(dateTimeString).matches()) return -1;
2431 
2432         ParsePosition pos = new ParsePosition(0);
2433         try {
2434             // The exif field is in local time. Parsing it as if it is UTC will yield time
2435             // since 1/1/1970 local time
2436             Date datetime;
2437             synchronized (sFormatter) {
2438                 datetime = sFormatter.parse(dateTimeString, pos);
2439             }
2440 
2441             if (offsetString != null) {
2442                 dateTimeString = dateTimeString + " " + offsetString;
2443                 ParsePosition position = new ParsePosition(0);
2444                 synchronized (sFormatterTz) {
2445                     datetime = sFormatterTz.parse(dateTimeString, position);
2446                 }
2447             }
2448 
2449             if (datetime == null) return -1;
2450             long msecs = datetime.getTime();
2451 
2452             if (subSecs != null) {
2453                 try {
2454                     long sub = Long.parseLong(subSecs);
2455                     while (sub > 1000) {
2456                         sub /= 10;
2457                     }
2458                     msecs += sub;
2459                 } catch (NumberFormatException e) {
2460                     // Ignored
2461                 }
2462             }
2463             return msecs;
2464         } catch (IllegalArgumentException e) {
2465             return -1;
2466         }
2467     }
2468 
2469     /**
2470      * Returns number of milliseconds since Jan. 1, 1970, midnight UTC.
2471      * Returns -1 if the date time information if not available.
2472      * @hide
2473      */
2474     @UnsupportedAppUsage
getGpsDateTime()2475     public long getGpsDateTime() {
2476         String date = getAttribute(TAG_GPS_DATESTAMP);
2477         String time = getAttribute(TAG_GPS_TIMESTAMP);
2478         if (date == null || time == null
2479                 || (!sNonZeroTimePattern.matcher(date).matches()
2480                 && !sNonZeroTimePattern.matcher(time).matches())) {
2481             return -1;
2482         }
2483 
2484         String dateTimeString = date + ' ' + time;
2485 
2486         ParsePosition pos = new ParsePosition(0);
2487         try {
2488             final Date datetime;
2489             synchronized (sFormatter) {
2490                 datetime = sFormatter.parse(dateTimeString, pos);
2491             }
2492             if (datetime == null) return -1;
2493             return datetime.getTime();
2494         } catch (IllegalArgumentException e) {
2495             return -1;
2496         }
2497     }
2498 
2499     /** {@hide} */
2500     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
convertRationalLatLonToFloat(String rationalString, String ref)2501     public static float convertRationalLatLonToFloat(String rationalString, String ref) {
2502         try {
2503             String [] parts = rationalString.split(",");
2504 
2505             String [] pair;
2506             pair = parts[0].split("/");
2507             double degrees = Double.parseDouble(pair[0].trim())
2508                     / Double.parseDouble(pair[1].trim());
2509 
2510             pair = parts[1].split("/");
2511             double minutes = Double.parseDouble(pair[0].trim())
2512                     / Double.parseDouble(pair[1].trim());
2513 
2514             pair = parts[2].split("/");
2515             double seconds = Double.parseDouble(pair[0].trim())
2516                     / Double.parseDouble(pair[1].trim());
2517 
2518             double result = degrees + (minutes / 60.0) + (seconds / 3600.0);
2519             if ((ref.equals("S") || ref.equals("W"))) {
2520                 return (float) -result;
2521             }
2522             return (float) result;
2523         } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
2524             // Not valid
2525             throw new IllegalArgumentException();
2526         }
2527     }
2528 
initForFilename(String filename)2529     private void initForFilename(String filename) throws IOException {
2530         FileInputStream in = null;
2531         mAssetInputStream = null;
2532         mFilename = filename;
2533         mIsInputStream = false;
2534         try {
2535             in = new FileInputStream(filename);
2536             if (isSeekableFD(in.getFD())) {
2537                 mSeekableFileDescriptor = in.getFD();
2538             } else {
2539                 mSeekableFileDescriptor = null;
2540             }
2541             loadAttributes(in);
2542         } finally {
2543             IoUtils.closeQuietly(in);
2544         }
2545     }
2546 
2547     // Checks the type of image file
getMimeType(BufferedInputStream in)2548     private int getMimeType(BufferedInputStream in) throws IOException {
2549         // TODO (b/142218289): Need to handle case where input stream does not support mark
2550         in.mark(SIGNATURE_CHECK_SIZE);
2551         byte[] signatureCheckBytes = new byte[SIGNATURE_CHECK_SIZE];
2552         in.read(signatureCheckBytes);
2553         in.reset();
2554         if (isJpegFormat(signatureCheckBytes)) {
2555             return IMAGE_TYPE_JPEG;
2556         } else if (isRafFormat(signatureCheckBytes)) {
2557             return IMAGE_TYPE_RAF;
2558         } else if (isHeifFormat(signatureCheckBytes)) {
2559             return IMAGE_TYPE_HEIF;
2560         } else if (isOrfFormat(signatureCheckBytes)) {
2561             return IMAGE_TYPE_ORF;
2562         } else if (isRw2Format(signatureCheckBytes)) {
2563             return IMAGE_TYPE_RW2;
2564         } else if (isPngFormat(signatureCheckBytes)) {
2565             return IMAGE_TYPE_PNG;
2566         } else if (isWebpFormat(signatureCheckBytes)) {
2567             return IMAGE_TYPE_WEBP;
2568         }
2569         // Certain file formats (PEF) are identified in readImageFileDirectory()
2570         return IMAGE_TYPE_UNKNOWN;
2571     }
2572 
2573     /**
2574      * This method looks at the first 3 bytes to determine if this file is a JPEG file.
2575      * See http://www.media.mit.edu/pia/Research/deepview/exif.html, "JPEG format and Marker"
2576      */
isJpegFormat(byte[] signatureCheckBytes)2577     private static boolean isJpegFormat(byte[] signatureCheckBytes) throws IOException {
2578         for (int i = 0; i < JPEG_SIGNATURE.length; i++) {
2579             if (signatureCheckBytes[i] != JPEG_SIGNATURE[i]) {
2580                 return false;
2581             }
2582         }
2583         return true;
2584     }
2585 
2586     /**
2587      * This method looks at the first 15 bytes to determine if this file is a RAF file.
2588      * There is no official specification for RAF files from Fuji, but there is an online archive of
2589      * image file specifications:
2590      * http://fileformats.archiveteam.org/wiki/Fujifilm_RAF
2591      */
isRafFormat(byte[] signatureCheckBytes)2592     private boolean isRafFormat(byte[] signatureCheckBytes) throws IOException {
2593         byte[] rafSignatureBytes = RAF_SIGNATURE.getBytes();
2594         for (int i = 0; i < rafSignatureBytes.length; i++) {
2595             if (signatureCheckBytes[i] != rafSignatureBytes[i]) {
2596                 return false;
2597             }
2598         }
2599         return true;
2600     }
2601 
isHeifFormat(byte[] signatureCheckBytes)2602     private boolean isHeifFormat(byte[] signatureCheckBytes) throws IOException {
2603         ByteOrderedDataInputStream signatureInputStream = null;
2604         try {
2605             signatureInputStream = new ByteOrderedDataInputStream(signatureCheckBytes);
2606             signatureInputStream.setByteOrder(ByteOrder.BIG_ENDIAN);
2607 
2608             long chunkSize = signatureInputStream.readInt();
2609             byte[] chunkType = new byte[4];
2610             signatureInputStream.read(chunkType);
2611 
2612             if (!Arrays.equals(chunkType, HEIF_TYPE_FTYP)) {
2613                 return false;
2614             }
2615 
2616             long chunkDataOffset = 8;
2617             if (chunkSize == 1) {
2618                 // This indicates that the next 8 bytes represent the chunk size,
2619                 // and chunk data comes after that.
2620                 chunkSize = signatureInputStream.readLong();
2621                 if (chunkSize < 16) {
2622                     // The smallest valid chunk is 16 bytes long in this case.
2623                     return false;
2624                 }
2625                 chunkDataOffset += 8;
2626             }
2627 
2628             // only sniff up to signatureCheckBytes.length
2629             if (chunkSize > signatureCheckBytes.length) {
2630                 chunkSize = signatureCheckBytes.length;
2631             }
2632 
2633             long chunkDataSize = chunkSize - chunkDataOffset;
2634 
2635             // It should at least have major brand (4-byte) and minor version (4-byte).
2636             // The rest of the chunk (if any) is a list of (4-byte) compatible brands.
2637             if (chunkDataSize < 8) {
2638                 return false;
2639             }
2640 
2641             byte[] brand = new byte[4];
2642             boolean isMif1 = false;
2643             boolean isHeic = false;
2644             for (long i = 0; i < chunkDataSize / 4;  ++i) {
2645                 if (signatureInputStream.read(brand) != brand.length) {
2646                     return false;
2647                 }
2648                 if (i == 1) {
2649                     // Skip this index, it refers to the minorVersion, not a brand.
2650                     continue;
2651                 }
2652                 if (Arrays.equals(brand, HEIF_BRAND_MIF1)) {
2653                     isMif1 = true;
2654                 } else if (Arrays.equals(brand, HEIF_BRAND_HEIC)) {
2655                     isHeic = true;
2656                 }
2657                 if (isMif1 && isHeic) {
2658                     return true;
2659                 }
2660             }
2661         } catch (Exception e) {
2662             if (DEBUG) {
2663                 Log.d(TAG, "Exception parsing HEIF file type box.", e);
2664             }
2665         } finally {
2666             if (signatureInputStream != null) {
2667                 signatureInputStream.close();
2668                 signatureInputStream = null;
2669             }
2670         }
2671         return false;
2672     }
2673 
2674     /**
2675      * ORF has a similar structure to TIFF but it contains a different signature at the TIFF Header.
2676      * This method looks at the 2 bytes following the Byte Order bytes to determine if this file is
2677      * an ORF file.
2678      * There is no official specification for ORF files from Olympus, but there is an online archive
2679      * of image file specifications:
2680      * http://fileformats.archiveteam.org/wiki/Olympus_ORF
2681      */
isOrfFormat(byte[] signatureCheckBytes)2682     private boolean isOrfFormat(byte[] signatureCheckBytes) throws IOException {
2683         ByteOrderedDataInputStream signatureInputStream = null;
2684 
2685         try {
2686             signatureInputStream = new ByteOrderedDataInputStream(signatureCheckBytes);
2687 
2688             // Read byte order
2689             mExifByteOrder = readByteOrder(signatureInputStream);
2690             // Set byte order
2691             signatureInputStream.setByteOrder(mExifByteOrder);
2692 
2693             short orfSignature = signatureInputStream.readShort();
2694             return orfSignature == ORF_SIGNATURE_1 || orfSignature == ORF_SIGNATURE_2;
2695         } catch (Exception e) {
2696             // Do nothing
2697         } finally {
2698             if (signatureInputStream != null) {
2699                 signatureInputStream.close();
2700             }
2701         }
2702         return false;
2703     }
2704 
2705     /**
2706      * RW2 is TIFF-based, but stores 0x55 signature byte instead of 0x42 at the header
2707      * See http://lclevy.free.fr/raw/
2708      */
isRw2Format(byte[] signatureCheckBytes)2709     private boolean isRw2Format(byte[] signatureCheckBytes) throws IOException {
2710         ByteOrderedDataInputStream signatureInputStream = null;
2711 
2712         try {
2713             signatureInputStream = new ByteOrderedDataInputStream(signatureCheckBytes);
2714 
2715             // Read byte order
2716             mExifByteOrder = readByteOrder(signatureInputStream);
2717             // Set byte order
2718             signatureInputStream.setByteOrder(mExifByteOrder);
2719 
2720             short signatureByte = signatureInputStream.readShort();
2721             signatureInputStream.close();
2722             return signatureByte == RW2_SIGNATURE;
2723         } catch (Exception e) {
2724             // Do nothing
2725         } finally {
2726             if (signatureInputStream != null) {
2727                 signatureInputStream.close();
2728             }
2729         }
2730         return false;
2731     }
2732 
2733     /**
2734      * PNG's file signature is first 8 bytes.
2735      * See PNG (Portable Network Graphics) Specification, Version 1.2, 3.1. PNG file signature
2736      */
isPngFormat(byte[] signatureCheckBytes)2737     private boolean isPngFormat(byte[] signatureCheckBytes) throws IOException {
2738         for (int i = 0; i < PNG_SIGNATURE.length; i++) {
2739             if (signatureCheckBytes[i] != PNG_SIGNATURE[i]) {
2740                 return false;
2741             }
2742         }
2743         return true;
2744     }
2745 
2746     /**
2747      * WebP's file signature is composed of 12 bytes:
2748      *   'RIFF' (4 bytes) + file length value (4 bytes) + 'WEBP' (4 bytes)
2749      * See https://developers.google.com/speed/webp/docs/riff_container, Section "WebP File Header"
2750      */
isWebpFormat(byte[] signatureCheckBytes)2751     private boolean isWebpFormat(byte[] signatureCheckBytes) throws IOException {
2752         for (int i = 0; i < WEBP_SIGNATURE_1.length; i++) {
2753             if (signatureCheckBytes[i] != WEBP_SIGNATURE_1[i]) {
2754                 return false;
2755             }
2756         }
2757         for (int i = 0; i < WEBP_SIGNATURE_2.length; i++) {
2758             if (signatureCheckBytes[i + WEBP_SIGNATURE_1.length + WEBP_FILE_SIZE_BYTE_LENGTH]
2759                     != WEBP_SIGNATURE_2[i]) {
2760                 return false;
2761             }
2762         }
2763         return true;
2764     }
2765 
isExifDataOnly(BufferedInputStream in)2766     private static boolean isExifDataOnly(BufferedInputStream in) throws IOException {
2767         in.mark(IDENTIFIER_EXIF_APP1.length);
2768         byte[] signatureCheckBytes = new byte[IDENTIFIER_EXIF_APP1.length];
2769         in.read(signatureCheckBytes);
2770         in.reset();
2771         for (int i = 0; i < IDENTIFIER_EXIF_APP1.length; i++) {
2772             if (signatureCheckBytes[i] != IDENTIFIER_EXIF_APP1[i]) {
2773                 return false;
2774             }
2775         }
2776         return true;
2777     }
2778 
2779     /**
2780      * Loads EXIF attributes from a JPEG input stream.
2781      *
2782      * @param in The input stream that starts with the JPEG data.
2783      * @param jpegOffset The offset value in input stream for JPEG data.
2784      * @param imageType The image type from which to retrieve metadata. Use IFD_TYPE_PRIMARY for
2785      *                   primary image, IFD_TYPE_PREVIEW for preview image, and
2786      *                   IFD_TYPE_THUMBNAIL for thumbnail image.
2787      * @throws IOException If the data contains invalid JPEG markers, offsets, or length values.
2788      */
getJpegAttributes(ByteOrderedDataInputStream in, int jpegOffset, int imageType)2789     private void getJpegAttributes(ByteOrderedDataInputStream in, int jpegOffset, int imageType)
2790             throws IOException {
2791         // See JPEG File Interchange Format Specification, "JFIF Specification"
2792         if (DEBUG) {
2793             Log.d(TAG, "getJpegAttributes starting with: " + in);
2794         }
2795 
2796         // JPEG uses Big Endian by default. See https://people.cs.umass.edu/~verts/cs32/endian.html
2797         in.setByteOrder(ByteOrder.BIG_ENDIAN);
2798 
2799         // Skip to JPEG data
2800         in.seek(jpegOffset);
2801         int bytesRead = jpegOffset;
2802 
2803         byte marker;
2804         if ((marker = in.readByte()) != MARKER) {
2805             throw new IOException("Invalid marker: " + Integer.toHexString(marker & 0xff));
2806         }
2807         ++bytesRead;
2808         if (in.readByte() != MARKER_SOI) {
2809             throw new IOException("Invalid marker: " + Integer.toHexString(marker & 0xff));
2810         }
2811         ++bytesRead;
2812         while (true) {
2813             marker = in.readByte();
2814             if (marker != MARKER) {
2815                 throw new IOException("Invalid marker:" + Integer.toHexString(marker & 0xff));
2816             }
2817             ++bytesRead;
2818             marker = in.readByte();
2819             if (DEBUG) {
2820                 Log.d(TAG, "Found JPEG segment indicator: " + Integer.toHexString(marker & 0xff));
2821             }
2822             ++bytesRead;
2823 
2824             // EOI indicates the end of an image and in case of SOS, JPEG image stream starts and
2825             // the image data will terminate right after.
2826             if (marker == MARKER_EOI || marker == MARKER_SOS) {
2827                 break;
2828             }
2829             int length = in.readUnsignedShort() - 2;
2830             bytesRead += 2;
2831             if (DEBUG) {
2832                 Log.d(TAG, "JPEG segment: " + Integer.toHexString(marker & 0xff) + " (length: "
2833                         + (length + 2) + ")");
2834             }
2835             if (length < 0) {
2836                 throw new IOException("Invalid length");
2837             }
2838             switch (marker) {
2839                 case MARKER_APP1: {
2840                     final int start = bytesRead;
2841                     final byte[] bytes = new byte[length];
2842                     in.readFully(bytes);
2843                     bytesRead += length;
2844                     length = 0;
2845 
2846                     if (ArrayUtils.startsWith(bytes, IDENTIFIER_EXIF_APP1)) {
2847                         final long offset = start + IDENTIFIER_EXIF_APP1.length;
2848                         final byte[] value = Arrays.copyOfRange(bytes,
2849                                 IDENTIFIER_EXIF_APP1.length, bytes.length);
2850                         // Save offset values for handleThumbnailFromJfif() function
2851                         mExifOffset = (int) offset;
2852                         readExifSegment(value, imageType);
2853                     } else if (ArrayUtils.startsWith(bytes, IDENTIFIER_XMP_APP1)) {
2854                         // See XMP Specification Part 3: Storage in Files, 1.1.3 JPEG, Table 6
2855                         final long offset = start + IDENTIFIER_XMP_APP1.length;
2856                         final byte[] value = Arrays.copyOfRange(bytes,
2857                                 IDENTIFIER_XMP_APP1.length, bytes.length);
2858                         // TODO: check if ignoring separate XMP data when tag 700 already exists is
2859                         //  valid.
2860                         if (getAttribute(TAG_XMP) == null) {
2861                             mAttributes[IFD_TYPE_PRIMARY].put(TAG_XMP, new ExifAttribute(
2862                                     IFD_FORMAT_BYTE, value.length, offset, value));
2863                             mXmpIsFromSeparateMarker = true;
2864                         }
2865                     }
2866                     break;
2867                 }
2868 
2869                 case MARKER_COM: {
2870                     byte[] bytes = new byte[length];
2871                     if (in.read(bytes) != length) {
2872                         throw new IOException("Invalid exif");
2873                     }
2874                     length = 0;
2875                     if (getAttribute(TAG_USER_COMMENT) == null) {
2876                         mAttributes[IFD_TYPE_EXIF].put(TAG_USER_COMMENT, ExifAttribute.createString(
2877                                 new String(bytes, ASCII)));
2878                     }
2879                     break;
2880                 }
2881 
2882                 case MARKER_SOF0:
2883                 case MARKER_SOF1:
2884                 case MARKER_SOF2:
2885                 case MARKER_SOF3:
2886                 case MARKER_SOF5:
2887                 case MARKER_SOF6:
2888                 case MARKER_SOF7:
2889                 case MARKER_SOF9:
2890                 case MARKER_SOF10:
2891                 case MARKER_SOF11:
2892                 case MARKER_SOF13:
2893                 case MARKER_SOF14:
2894                 case MARKER_SOF15: {
2895                     if (in.skipBytes(1) != 1) {
2896                         throw new IOException("Invalid SOFx");
2897                     }
2898                     mAttributes[imageType].put(TAG_IMAGE_LENGTH, ExifAttribute.createULong(
2899                             in.readUnsignedShort(), mExifByteOrder));
2900                     mAttributes[imageType].put(TAG_IMAGE_WIDTH, ExifAttribute.createULong(
2901                             in.readUnsignedShort(), mExifByteOrder));
2902                     length -= 5;
2903                     break;
2904                 }
2905 
2906                 default: {
2907                     break;
2908                 }
2909             }
2910             if (length < 0) {
2911                 throw new IOException("Invalid length");
2912             }
2913             if (in.skipBytes(length) != length) {
2914                 throw new IOException("Invalid JPEG segment");
2915             }
2916             bytesRead += length;
2917         }
2918         // Restore original byte order
2919         in.setByteOrder(mExifByteOrder);
2920     }
2921 
getRawAttributes(ByteOrderedDataInputStream in)2922     private void getRawAttributes(ByteOrderedDataInputStream in) throws IOException {
2923         // Parse TIFF Headers. See JEITA CP-3451C Section 4.5.2. Table 1.
2924         parseTiffHeaders(in, in.available());
2925 
2926         // Read TIFF image file directories. See JEITA CP-3451C Section 4.5.2. Figure 6.
2927         readImageFileDirectory(in, IFD_TYPE_PRIMARY);
2928 
2929         // Update ImageLength/Width tags for all image data.
2930         updateImageSizeValues(in, IFD_TYPE_PRIMARY);
2931         updateImageSizeValues(in, IFD_TYPE_PREVIEW);
2932         updateImageSizeValues(in, IFD_TYPE_THUMBNAIL);
2933 
2934         // Check if each image data is in valid position.
2935         validateImages();
2936 
2937         if (mMimeType == IMAGE_TYPE_PEF) {
2938             // PEF files contain a MakerNote data, which contains the data for ColorSpace tag.
2939             // See http://lclevy.free.fr/raw/ and piex.cc PefGetPreviewData()
2940             ExifAttribute makerNoteAttribute =
2941                     (ExifAttribute) mAttributes[IFD_TYPE_EXIF].get(TAG_MAKER_NOTE);
2942             if (makerNoteAttribute != null) {
2943                 // Create an ordered DataInputStream for MakerNote
2944                 ByteOrderedDataInputStream makerNoteDataInputStream =
2945                         new ByteOrderedDataInputStream(makerNoteAttribute.bytes);
2946                 makerNoteDataInputStream.setByteOrder(mExifByteOrder);
2947 
2948                 // Seek to MakerNote data
2949                 makerNoteDataInputStream.seek(PEF_MAKER_NOTE_SKIP_SIZE);
2950 
2951                 // Read IFD data from MakerNote
2952                 readImageFileDirectory(makerNoteDataInputStream, IFD_TYPE_PEF);
2953 
2954                 // Update ColorSpace tag
2955                 ExifAttribute colorSpaceAttribute =
2956                         (ExifAttribute) mAttributes[IFD_TYPE_PEF].get(TAG_COLOR_SPACE);
2957                 if (colorSpaceAttribute != null) {
2958                     mAttributes[IFD_TYPE_EXIF].put(TAG_COLOR_SPACE, colorSpaceAttribute);
2959                 }
2960             }
2961         }
2962     }
2963 
2964     /**
2965      * RAF files contains a JPEG and a CFA data.
2966      * The JPEG contains two images, a preview and a thumbnail, while the CFA contains a RAW image.
2967      * This method looks at the first 160 bytes of a RAF file to retrieve the offset and length
2968      * values for the JPEG and CFA data.
2969      * Using that data, it parses the JPEG data to retrieve the preview and thumbnail image data,
2970      * then parses the CFA metadata to retrieve the primary image length/width values.
2971      * For data format details, see http://fileformats.archiveteam.org/wiki/Fujifilm_RAF
2972      */
getRafAttributes(ByteOrderedDataInputStream in)2973     private void getRafAttributes(ByteOrderedDataInputStream in) throws IOException {
2974         // Retrieve offset & length values
2975         in.skipBytes(RAF_OFFSET_TO_JPEG_IMAGE_OFFSET);
2976         byte[] jpegOffsetBytes = new byte[4];
2977         byte[] cfaHeaderOffsetBytes = new byte[4];
2978         in.read(jpegOffsetBytes);
2979         // Skip JPEG length value since it is not needed
2980         in.skipBytes(RAF_JPEG_LENGTH_VALUE_SIZE);
2981         in.read(cfaHeaderOffsetBytes);
2982         int rafJpegOffset = ByteBuffer.wrap(jpegOffsetBytes).getInt();
2983         int rafCfaHeaderOffset = ByteBuffer.wrap(cfaHeaderOffsetBytes).getInt();
2984 
2985         // Retrieve JPEG image metadata
2986         getJpegAttributes(in, rafJpegOffset, IFD_TYPE_PREVIEW);
2987 
2988         // Skip to CFA header offset.
2989         in.seek(rafCfaHeaderOffset);
2990 
2991         // Retrieve primary image length/width values, if TAG_RAF_IMAGE_SIZE exists
2992         in.setByteOrder(ByteOrder.BIG_ENDIAN);
2993         int numberOfDirectoryEntry = in.readInt();
2994         if (DEBUG) {
2995             Log.d(TAG, "numberOfDirectoryEntry: " + numberOfDirectoryEntry);
2996         }
2997         // CFA stores some metadata about the RAW image. Since CFA uses proprietary tags, can only
2998         // find and retrieve image size information tags, while skipping others.
2999         // See piex.cc RafGetDimension()
3000         for (int i = 0; i < numberOfDirectoryEntry; ++i) {
3001             int tagNumber = in.readUnsignedShort();
3002             int numberOfBytes = in.readUnsignedShort();
3003             if (tagNumber == TAG_RAF_IMAGE_SIZE.number) {
3004                 int imageLength = in.readShort();
3005                 int imageWidth = in.readShort();
3006                 ExifAttribute imageLengthAttribute =
3007                         ExifAttribute.createUShort(imageLength, mExifByteOrder);
3008                 ExifAttribute imageWidthAttribute =
3009                         ExifAttribute.createUShort(imageWidth, mExifByteOrder);
3010                 mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH, imageLengthAttribute);
3011                 mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH, imageWidthAttribute);
3012                 if (DEBUG) {
3013                     Log.d(TAG, "Updated to length: " + imageLength + ", width: " + imageWidth);
3014                 }
3015                 return;
3016             }
3017             in.skipBytes(numberOfBytes);
3018         }
3019     }
3020 
getHeifAttributes(ByteOrderedDataInputStream in)3021     private void getHeifAttributes(ByteOrderedDataInputStream in) throws IOException {
3022         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
3023         try {
3024             retriever.setDataSource(new MediaDataSource() {
3025                 long mPosition;
3026 
3027                 @Override
3028                 public void close() throws IOException {}
3029 
3030                 @Override
3031                 public int readAt(long position, byte[] buffer, int offset, int size)
3032                         throws IOException {
3033                     if (size == 0) {
3034                         return 0;
3035                     }
3036                     if (position < 0) {
3037                         return -1;
3038                     }
3039                     try {
3040                         if (mPosition != position) {
3041                             // We don't allow seek to positions after the available bytes,
3042                             // the input stream won't be able to seek back then.
3043                             // However, if we hit an exception before (mPosition set to -1),
3044                             // let it try the seek in hope it might recover.
3045                             if (mPosition >= 0 && position >= mPosition + in.available()) {
3046                                 return -1;
3047                             }
3048                             in.seek(position);
3049                             mPosition = position;
3050                         }
3051 
3052                         // If the read will cause us to go over the available bytes,
3053                         // reduce the size so that we stay in the available range.
3054                         // Otherwise the input stream may not be able to seek back.
3055                         if (size > in.available()) {
3056                             size = in.available();
3057                         }
3058 
3059                         int bytesRead = in.read(buffer, offset, size);
3060                         if (bytesRead >= 0) {
3061                             mPosition += bytesRead;
3062                             return bytesRead;
3063                         }
3064                     } catch (IOException e) {}
3065                     mPosition = -1; // need to seek on next read
3066                     return -1;
3067                 }
3068 
3069                 @Override
3070                 public long getSize() throws IOException {
3071                     return -1;
3072                 }
3073             });
3074 
3075             String exifOffsetStr = retriever.extractMetadata(
3076                     MediaMetadataRetriever.METADATA_KEY_EXIF_OFFSET);
3077             String exifLengthStr = retriever.extractMetadata(
3078                     MediaMetadataRetriever.METADATA_KEY_EXIF_LENGTH);
3079             String hasImage = retriever.extractMetadata(
3080                     MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
3081             String hasVideo = retriever.extractMetadata(
3082                     MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
3083 
3084             String width = null;
3085             String height = null;
3086             String rotation = null;
3087             final String METADATA_VALUE_YES = "yes";
3088             // If the file has both image and video, prefer image info over video info.
3089             // App querying ExifInterface is most likely using the bitmap path which
3090             // picks the image first.
3091             if (METADATA_VALUE_YES.equals(hasImage)) {
3092                 width = retriever.extractMetadata(
3093                         MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH);
3094                 height = retriever.extractMetadata(
3095                         MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT);
3096                 rotation = retriever.extractMetadata(
3097                         MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION);
3098             } else if (METADATA_VALUE_YES.equals(hasVideo)) {
3099                 width = retriever.extractMetadata(
3100                         MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
3101                 height = retriever.extractMetadata(
3102                         MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
3103                 rotation = retriever.extractMetadata(
3104                         MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
3105             }
3106 
3107             if (width != null) {
3108                 mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH,
3109                         ExifAttribute.createUShort(Integer.parseInt(width), mExifByteOrder));
3110             }
3111 
3112             if (height != null) {
3113                 mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH,
3114                         ExifAttribute.createUShort(Integer.parseInt(height), mExifByteOrder));
3115             }
3116 
3117             if (rotation != null) {
3118                 int orientation = ExifInterface.ORIENTATION_NORMAL;
3119 
3120                 // all rotation angles in CW
3121                 switch (Integer.parseInt(rotation)) {
3122                     case 90:
3123                         orientation = ExifInterface.ORIENTATION_ROTATE_90;
3124                         break;
3125                     case 180:
3126                         orientation = ExifInterface.ORIENTATION_ROTATE_180;
3127                         break;
3128                     case 270:
3129                         orientation = ExifInterface.ORIENTATION_ROTATE_270;
3130                         break;
3131                 }
3132 
3133                 mAttributes[IFD_TYPE_PRIMARY].put(TAG_ORIENTATION,
3134                         ExifAttribute.createUShort(orientation, mExifByteOrder));
3135             }
3136 
3137             if (exifOffsetStr != null && exifLengthStr != null) {
3138                 int offset = Integer.parseInt(exifOffsetStr);
3139                 int length = Integer.parseInt(exifLengthStr);
3140                 if (length <= 6) {
3141                     throw new IOException("Invalid exif length");
3142                 }
3143                 in.seek(offset);
3144                 byte[] identifier = new byte[6];
3145                 if (in.read(identifier) != 6) {
3146                     throw new IOException("Can't read identifier");
3147                 }
3148                 offset += 6;
3149                 length -= 6;
3150                 if (!Arrays.equals(identifier, IDENTIFIER_EXIF_APP1)) {
3151                     throw new IOException("Invalid identifier");
3152                 }
3153 
3154                 // TODO: Need to handle potential OutOfMemoryError
3155                 byte[] bytes = new byte[length];
3156                 if (in.read(bytes) != length) {
3157                     throw new IOException("Can't read exif");
3158                 }
3159                 // Save offset values for handling thumbnail and attribute offsets.
3160                 mExifOffset = offset;
3161                 readExifSegment(bytes, IFD_TYPE_PRIMARY);
3162             }
3163 
3164             if (DEBUG) {
3165                 Log.d(TAG, "Heif meta: " + width + "x" + height + ", rotation " + rotation);
3166             }
3167         } finally {
3168             retriever.release();
3169         }
3170     }
3171 
getStandaloneAttributes(ByteOrderedDataInputStream in)3172     private void getStandaloneAttributes(ByteOrderedDataInputStream in) throws IOException {
3173         in.skipBytes(IDENTIFIER_EXIF_APP1.length);
3174         // TODO: Need to handle potential OutOfMemoryError
3175         byte[] data = new byte[in.available()];
3176         in.readFully(data);
3177         // Save offset values for handling thumbnail and attribute offsets.
3178         mExifOffset = IDENTIFIER_EXIF_APP1.length;
3179         readExifSegment(data, IFD_TYPE_PRIMARY);
3180     }
3181 
3182     /**
3183      * ORF files contains a primary image data and a MakerNote data that contains preview/thumbnail
3184      * images. Both data takes the form of IFDs and can therefore be read with the
3185      * readImageFileDirectory() method.
3186      * This method reads all the necessary data and updates the primary/preview/thumbnail image
3187      * information according to the GetOlympusPreviewImage() method in piex.cc.
3188      * For data format details, see the following:
3189      * http://fileformats.archiveteam.org/wiki/Olympus_ORF
3190      * https://libopenraw.freedesktop.org/wiki/Olympus_ORF
3191      */
getOrfAttributes(ByteOrderedDataInputStream in)3192     private void getOrfAttributes(ByteOrderedDataInputStream in) throws IOException {
3193         // Retrieve primary image data
3194         // Other Exif data will be located in the Makernote.
3195         getRawAttributes(in);
3196 
3197         // Additionally retrieve preview/thumbnail information from MakerNote tag, which contains
3198         // proprietary tags and therefore does not have offical documentation
3199         // See GetOlympusPreviewImage() in piex.cc & http://www.exiv2.org/tags-olympus.html
3200         ExifAttribute makerNoteAttribute =
3201                 (ExifAttribute) mAttributes[IFD_TYPE_EXIF].get(TAG_MAKER_NOTE);
3202         if (makerNoteAttribute != null) {
3203             // Create an ordered DataInputStream for MakerNote
3204             ByteOrderedDataInputStream makerNoteDataInputStream =
3205                     new ByteOrderedDataInputStream(makerNoteAttribute.bytes);
3206             makerNoteDataInputStream.setByteOrder(mExifByteOrder);
3207 
3208             // There are two types of headers for Olympus MakerNotes
3209             // See http://www.exiv2.org/makernote.html#R1
3210             byte[] makerNoteHeader1Bytes = new byte[ORF_MAKER_NOTE_HEADER_1.length];
3211             makerNoteDataInputStream.readFully(makerNoteHeader1Bytes);
3212             makerNoteDataInputStream.seek(0);
3213             byte[] makerNoteHeader2Bytes = new byte[ORF_MAKER_NOTE_HEADER_2.length];
3214             makerNoteDataInputStream.readFully(makerNoteHeader2Bytes);
3215             // Skip the corresponding amount of bytes for each header type
3216             if (Arrays.equals(makerNoteHeader1Bytes, ORF_MAKER_NOTE_HEADER_1)) {
3217                 makerNoteDataInputStream.seek(ORF_MAKER_NOTE_HEADER_1_SIZE);
3218             } else if (Arrays.equals(makerNoteHeader2Bytes, ORF_MAKER_NOTE_HEADER_2)) {
3219                 makerNoteDataInputStream.seek(ORF_MAKER_NOTE_HEADER_2_SIZE);
3220             }
3221 
3222             // Read IFD data from MakerNote
3223             readImageFileDirectory(makerNoteDataInputStream, IFD_TYPE_ORF_MAKER_NOTE);
3224 
3225             // Retrieve & update preview image offset & length values
3226             ExifAttribute imageLengthAttribute = (ExifAttribute)
3227                     mAttributes[IFD_TYPE_ORF_CAMERA_SETTINGS].get(TAG_ORF_PREVIEW_IMAGE_START);
3228             ExifAttribute bitsPerSampleAttribute = (ExifAttribute)
3229                     mAttributes[IFD_TYPE_ORF_CAMERA_SETTINGS].get(TAG_ORF_PREVIEW_IMAGE_LENGTH);
3230 
3231             if (imageLengthAttribute != null && bitsPerSampleAttribute != null) {
3232                 mAttributes[IFD_TYPE_PREVIEW].put(TAG_JPEG_INTERCHANGE_FORMAT,
3233                         imageLengthAttribute);
3234                 mAttributes[IFD_TYPE_PREVIEW].put(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
3235                         bitsPerSampleAttribute);
3236             }
3237 
3238             // TODO: Check this behavior in other ORF files
3239             // Retrieve primary image length & width values
3240             // See piex.cc GetOlympusPreviewImage()
3241             ExifAttribute aspectFrameAttribute = (ExifAttribute)
3242                     mAttributes[IFD_TYPE_ORF_IMAGE_PROCESSING].get(TAG_ORF_ASPECT_FRAME);
3243             if (aspectFrameAttribute != null) {
3244                 int[] aspectFrameValues = new int[4];
3245                 aspectFrameValues = (int[]) aspectFrameAttribute.getValue(mExifByteOrder);
3246                 if (aspectFrameValues[2] > aspectFrameValues[0] &&
3247                         aspectFrameValues[3] > aspectFrameValues[1]) {
3248                     int primaryImageWidth = aspectFrameValues[2] - aspectFrameValues[0] + 1;
3249                     int primaryImageLength = aspectFrameValues[3] - aspectFrameValues[1] + 1;
3250                     // Swap width & length values
3251                     if (primaryImageWidth < primaryImageLength) {
3252                         primaryImageWidth += primaryImageLength;
3253                         primaryImageLength = primaryImageWidth - primaryImageLength;
3254                         primaryImageWidth -= primaryImageLength;
3255                     }
3256                     ExifAttribute primaryImageWidthAttribute =
3257                             ExifAttribute.createUShort(primaryImageWidth, mExifByteOrder);
3258                     ExifAttribute primaryImageLengthAttribute =
3259                             ExifAttribute.createUShort(primaryImageLength, mExifByteOrder);
3260 
3261                     mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH, primaryImageWidthAttribute);
3262                     mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH, primaryImageLengthAttribute);
3263                 }
3264             }
3265         }
3266     }
3267 
3268     // RW2 contains the primary image data in IFD0 and the preview and/or thumbnail image data in
3269     // the JpgFromRaw tag
3270     // See https://libopenraw.freedesktop.org/wiki/Panasonic_RAW/ and piex.cc Rw2GetPreviewData()
getRw2Attributes(ByteOrderedDataInputStream in)3271     private void getRw2Attributes(ByteOrderedDataInputStream in) throws IOException {
3272         // Retrieve primary image data
3273         getRawAttributes(in);
3274 
3275         // Retrieve preview and/or thumbnail image data
3276         ExifAttribute jpgFromRawAttribute =
3277                 (ExifAttribute) mAttributes[IFD_TYPE_PRIMARY].get(TAG_RW2_JPG_FROM_RAW);
3278         if (jpgFromRawAttribute != null) {
3279             getJpegAttributes(in, mRw2JpgFromRawOffset, IFD_TYPE_PREVIEW);
3280         }
3281 
3282         // Set ISO tag value if necessary
3283         ExifAttribute rw2IsoAttribute =
3284                 (ExifAttribute) mAttributes[IFD_TYPE_PRIMARY].get(TAG_RW2_ISO);
3285         ExifAttribute exifIsoAttribute =
3286                 (ExifAttribute) mAttributes[IFD_TYPE_EXIF].get(TAG_ISO_SPEED_RATINGS);
3287         if (rw2IsoAttribute != null && exifIsoAttribute == null) {
3288             // Place this attribute only if it doesn't exist
3289             mAttributes[IFD_TYPE_EXIF].put(TAG_ISO_SPEED_RATINGS, rw2IsoAttribute);
3290         }
3291     }
3292 
3293     // PNG contains the EXIF data as a Special-Purpose Chunk
getPngAttributes(ByteOrderedDataInputStream in)3294     private void getPngAttributes(ByteOrderedDataInputStream in) throws IOException {
3295         if (DEBUG) {
3296             Log.d(TAG, "getPngAttributes starting with: " + in);
3297         }
3298 
3299         // PNG uses Big Endian by default.
3300         // See PNG (Portable Network Graphics) Specification, Version 1.2,
3301         // 2.1. Integers and byte order
3302         in.setByteOrder(ByteOrder.BIG_ENDIAN);
3303 
3304         int bytesRead = 0;
3305 
3306         // Skip the signature bytes
3307         in.skipBytes(PNG_SIGNATURE.length);
3308         bytesRead += PNG_SIGNATURE.length;
3309 
3310         // Each chunk is made up of four parts:
3311         //   1) Length: 4-byte unsigned integer indicating the number of bytes in the
3312         //   Chunk Data field. Excludes Chunk Type and CRC bytes.
3313         //   2) Chunk Type: 4-byte chunk type code.
3314         //   3) Chunk Data: The data bytes. Can be zero-length.
3315         //   4) CRC: 4-byte data calculated on the preceding bytes in the chunk. Always
3316         //   present.
3317         // --> 4 (length bytes) + 4 (type bytes) + X (data bytes) + 4 (CRC bytes)
3318         // See PNG (Portable Network Graphics) Specification, Version 1.2,
3319         // 3.2. Chunk layout
3320         try {
3321             while (true) {
3322                 int length = in.readInt();
3323                 bytesRead += 4;
3324 
3325                 byte[] type = new byte[PNG_CHUNK_TYPE_BYTE_LENGTH];
3326                 if (in.read(type) != type.length) {
3327                     throw new IOException("Encountered invalid length while parsing PNG chunk"
3328                             + "type");
3329                 }
3330                 bytesRead += PNG_CHUNK_TYPE_BYTE_LENGTH;
3331 
3332                 // The first chunk must be the IHDR chunk
3333                 if (bytesRead == 16 && !Arrays.equals(type, PNG_CHUNK_TYPE_IHDR)) {
3334                     throw new IOException("Encountered invalid PNG file--IHDR chunk should appear"
3335                             + "as the first chunk");
3336                 }
3337 
3338                 if (Arrays.equals(type, PNG_CHUNK_TYPE_IEND)) {
3339                     // IEND marks the end of the image.
3340                     break;
3341                 } else if (Arrays.equals(type, PNG_CHUNK_TYPE_EXIF)) {
3342                     // TODO: Need to handle potential OutOfMemoryError
3343                     byte[] data = new byte[length];
3344                     if (in.read(data) != length) {
3345                         throw new IOException("Failed to read given length for given PNG chunk "
3346                                 + "type: " + byteArrayToHexString(type));
3347                     }
3348 
3349                     // Compare CRC values for potential data corruption.
3350                     int dataCrcValue = in.readInt();
3351                     // Cyclic Redundancy Code used to check for corruption of the data
3352                     CRC32 crc = new CRC32();
3353                     crc.update(type);
3354                     crc.update(data);
3355                     if ((int) crc.getValue() != dataCrcValue) {
3356                         throw new IOException("Encountered invalid CRC value for PNG-EXIF chunk."
3357                                 + "\n recorded CRC value: " + dataCrcValue + ", calculated CRC "
3358                                 + "value: " + crc.getValue());
3359                     }
3360                     // Save offset values for handleThumbnailFromJfif() function
3361                     mExifOffset = bytesRead;
3362                     readExifSegment(data, IFD_TYPE_PRIMARY);
3363 
3364                     validateImages();
3365                     break;
3366                 } else {
3367                     // Skip to next chunk
3368                     in.skipBytes(length + PNG_CHUNK_CRC_BYTE_LENGTH);
3369                     bytesRead += length + PNG_CHUNK_CRC_BYTE_LENGTH;
3370                 }
3371             }
3372         } catch (EOFException e) {
3373             // Should not reach here. Will only reach here if the file is corrupted or
3374             // does not follow the PNG specifications
3375             throw new IOException("Encountered corrupt PNG file.");
3376         }
3377     }
3378 
3379     // WebP contains EXIF data as a RIFF File Format Chunk
3380     // All references below can be found in the following link.
3381     // https://developers.google.com/speed/webp/docs/riff_container
getWebpAttributes(ByteOrderedDataInputStream in)3382     private void getWebpAttributes(ByteOrderedDataInputStream in) throws IOException {
3383         if (DEBUG) {
3384             Log.d(TAG, "getWebpAttributes starting with: " + in);
3385         }
3386         // WebP uses little-endian by default.
3387         // See Section "Terminology & Basics"
3388         in.setByteOrder(ByteOrder.LITTLE_ENDIAN);
3389         in.skipBytes(WEBP_SIGNATURE_1.length);
3390         // File size corresponds to the size of the entire file from offset 8.
3391         // See Section "WebP File Header"
3392         int fileSize = in.readInt() + 8;
3393         int bytesRead = 8;
3394         bytesRead += in.skipBytes(WEBP_SIGNATURE_2.length);
3395         try {
3396             while (true) {
3397                 // Each chunk is made up of three parts:
3398                 //   1) Chunk FourCC: 4-byte concatenating four ASCII characters.
3399                 //   2) Chunk Size: 4-byte unsigned integer indicating the size of the chunk.
3400                 //                  Excludes Chunk FourCC and Chunk Size bytes.
3401                 //   3) Chunk Payload: data payload. A single padding byte ('0') is added if
3402                 //                     Chunk Size is odd.
3403                 // See Section "RIFF File Format"
3404                 byte[] code = new byte[WEBP_CHUNK_TYPE_BYTE_LENGTH];
3405                 if (in.read(code) != code.length) {
3406                     throw new IOException("Encountered invalid length while parsing WebP chunk"
3407                             + "type");
3408                 }
3409                 bytesRead += 4;
3410                 int chunkSize = in.readInt();
3411                 bytesRead += 4;
3412                 if (Arrays.equals(WEBP_CHUNK_TYPE_EXIF, code)) {
3413                     // TODO: Need to handle potential OutOfMemoryError
3414                     byte[] payload = new byte[chunkSize];
3415                     if (in.read(payload) != chunkSize) {
3416                         throw new IOException("Failed to read given length for given PNG chunk "
3417                                 + "type: " + byteArrayToHexString(code));
3418                     }
3419                     // Save offset values for handling thumbnail and attribute offsets.
3420                     mExifOffset = bytesRead;
3421                     readExifSegment(payload, IFD_TYPE_PRIMARY);
3422                     break;
3423                 } else {
3424                     // Add a single padding byte at end if chunk size is odd
3425                     chunkSize = (chunkSize % 2 == 1) ? chunkSize + 1 : chunkSize;
3426                     // Check if skipping to next chunk is necessary
3427                     if (bytesRead + chunkSize == fileSize) {
3428                         // Reached end of file
3429                         break;
3430                     } else if (bytesRead + chunkSize > fileSize) {
3431                         throw new IOException("Encountered WebP file with invalid chunk size");
3432                     }
3433                     // Skip to next chunk
3434                     int skipped = in.skipBytes(chunkSize);
3435                     if (skipped != chunkSize) {
3436                         throw new IOException("Encountered WebP file with invalid chunk size");
3437                     }
3438                     bytesRead += skipped;
3439                 }
3440             }
3441         } catch (EOFException e) {
3442             // Should not reach here. Will only reach here if the file is corrupted or
3443             // does not follow the WebP specifications
3444             throw new IOException("Encountered corrupt WebP file.");
3445         }
3446     }
3447 
3448     // Stores a new JPEG image with EXIF attributes into a given output stream.
saveJpegAttributes(InputStream inputStream, OutputStream outputStream)3449     private void saveJpegAttributes(InputStream inputStream, OutputStream outputStream)
3450             throws IOException {
3451         // See JPEG File Interchange Format Specification, "JFIF Specification"
3452         if (DEBUG) {
3453             Log.d(TAG, "saveJpegAttributes starting with (inputStream: " + inputStream
3454                     + ", outputStream: " + outputStream + ")");
3455         }
3456         DataInputStream dataInputStream = new DataInputStream(inputStream);
3457         ByteOrderedDataOutputStream dataOutputStream =
3458                 new ByteOrderedDataOutputStream(outputStream, ByteOrder.BIG_ENDIAN);
3459         if (dataInputStream.readByte() != MARKER) {
3460             throw new IOException("Invalid marker");
3461         }
3462         dataOutputStream.writeByte(MARKER);
3463         if (dataInputStream.readByte() != MARKER_SOI) {
3464             throw new IOException("Invalid marker");
3465         }
3466         dataOutputStream.writeByte(MARKER_SOI);
3467 
3468         // Remove XMP data if it is from a separate marker (IDENTIFIER_XMP_APP1, not
3469         // IDENTIFIER_EXIF_APP1)
3470         // Will re-add it later after the rest of the file is written
3471         ExifAttribute xmpAttribute = null;
3472         if (getAttribute(TAG_XMP) != null && mXmpIsFromSeparateMarker) {
3473             xmpAttribute = (ExifAttribute) mAttributes[IFD_TYPE_PRIMARY].remove(TAG_XMP);
3474         }
3475 
3476         // Write EXIF APP1 segment
3477         dataOutputStream.writeByte(MARKER);
3478         dataOutputStream.writeByte(MARKER_APP1);
3479         writeExifSegment(dataOutputStream);
3480 
3481         // Re-add previously removed XMP data.
3482         if (xmpAttribute != null) {
3483             mAttributes[IFD_TYPE_PRIMARY].put(TAG_XMP, xmpAttribute);
3484         }
3485 
3486         byte[] bytes = new byte[4096];
3487 
3488         while (true) {
3489             byte marker = dataInputStream.readByte();
3490             if (marker != MARKER) {
3491                 throw new IOException("Invalid marker");
3492             }
3493             marker = dataInputStream.readByte();
3494             switch (marker) {
3495                 case MARKER_APP1: {
3496                     int length = dataInputStream.readUnsignedShort() - 2;
3497                     if (length < 0) {
3498                         throw new IOException("Invalid length");
3499                     }
3500                     byte[] identifier = new byte[6];
3501                     if (length >= 6) {
3502                         if (dataInputStream.read(identifier) != 6) {
3503                             throw new IOException("Invalid exif");
3504                         }
3505                         if (Arrays.equals(identifier, IDENTIFIER_EXIF_APP1)) {
3506                             // Skip the original EXIF APP1 segment.
3507                             if (dataInputStream.skipBytes(length - 6) != length - 6) {
3508                                 throw new IOException("Invalid length");
3509                             }
3510                             break;
3511                         }
3512                     }
3513                     // Copy non-EXIF APP1 segment.
3514                     dataOutputStream.writeByte(MARKER);
3515                     dataOutputStream.writeByte(marker);
3516                     dataOutputStream.writeUnsignedShort(length + 2);
3517                     if (length >= 6) {
3518                         length -= 6;
3519                         dataOutputStream.write(identifier);
3520                     }
3521                     int read;
3522                     while (length > 0 && (read = dataInputStream.read(
3523                             bytes, 0, Math.min(length, bytes.length))) >= 0) {
3524                         dataOutputStream.write(bytes, 0, read);
3525                         length -= read;
3526                     }
3527                     break;
3528                 }
3529                 case MARKER_EOI:
3530                 case MARKER_SOS: {
3531                     dataOutputStream.writeByte(MARKER);
3532                     dataOutputStream.writeByte(marker);
3533                     // Copy all the remaining data
3534                     Streams.copy(dataInputStream, dataOutputStream);
3535                     return;
3536                 }
3537                 default: {
3538                     // Copy JPEG segment
3539                     dataOutputStream.writeByte(MARKER);
3540                     dataOutputStream.writeByte(marker);
3541                     int length = dataInputStream.readUnsignedShort();
3542                     dataOutputStream.writeUnsignedShort(length);
3543                     length -= 2;
3544                     if (length < 0) {
3545                         throw new IOException("Invalid length");
3546                     }
3547                     int read;
3548                     while (length > 0 && (read = dataInputStream.read(
3549                             bytes, 0, Math.min(length, bytes.length))) >= 0) {
3550                         dataOutputStream.write(bytes, 0, read);
3551                         length -= read;
3552                     }
3553                     break;
3554                 }
3555             }
3556         }
3557     }
3558 
savePngAttributes(InputStream inputStream, OutputStream outputStream)3559     private void savePngAttributes(InputStream inputStream, OutputStream outputStream)
3560             throws IOException {
3561         if (DEBUG) {
3562             Log.d(TAG, "savePngAttributes starting with (inputStream: " + inputStream
3563                     + ", outputStream: " + outputStream + ")");
3564         }
3565         DataInputStream dataInputStream = new DataInputStream(inputStream);
3566         ByteOrderedDataOutputStream dataOutputStream =
3567                 new ByteOrderedDataOutputStream(outputStream, ByteOrder.BIG_ENDIAN);
3568         // Copy PNG signature bytes
3569         copy(dataInputStream, dataOutputStream, PNG_SIGNATURE.length);
3570         // EXIF chunk can appear anywhere between the first (IHDR) and last (IEND) chunks, except
3571         // between IDAT chunks.
3572         // Adhering to these rules,
3573         //   1) if EXIF chunk did not exist in the original file, it will be stored right after the
3574         //      first chunk,
3575         //   2) if EXIF chunk existed in the original file, it will be stored in the same location.
3576         if (mExifOffset == 0) {
3577             // Copy IHDR chunk bytes
3578             int ihdrChunkLength = dataInputStream.readInt();
3579             dataOutputStream.writeInt(ihdrChunkLength);
3580             copy(dataInputStream, dataOutputStream, PNG_CHUNK_TYPE_BYTE_LENGTH
3581                     + ihdrChunkLength + PNG_CHUNK_CRC_BYTE_LENGTH);
3582         } else {
3583             // Copy up until the point where EXIF chunk length information is stored.
3584             int copyLength = mExifOffset - PNG_SIGNATURE.length
3585                     - 4 /* PNG EXIF chunk length bytes */
3586                     - PNG_CHUNK_TYPE_BYTE_LENGTH;
3587             copy(dataInputStream, dataOutputStream, copyLength);
3588             // Skip to the start of the chunk after the EXIF chunk
3589             int exifChunkLength = dataInputStream.readInt();
3590             dataInputStream.skipBytes(PNG_CHUNK_TYPE_BYTE_LENGTH + exifChunkLength
3591                     + PNG_CHUNK_CRC_BYTE_LENGTH);
3592         }
3593         // Write EXIF data
3594         try (ByteArrayOutputStream exifByteArrayOutputStream = new ByteArrayOutputStream()) {
3595             // A byte array is needed to calculate the CRC value of this chunk which requires
3596             // the chunk type bytes and the chunk data bytes.
3597             ByteOrderedDataOutputStream exifDataOutputStream =
3598                     new ByteOrderedDataOutputStream(exifByteArrayOutputStream,
3599                             ByteOrder.BIG_ENDIAN);
3600             // Store Exif data in separate byte array
3601             writeExifSegment(exifDataOutputStream);
3602             byte[] exifBytes =
3603                     ((ByteArrayOutputStream) exifDataOutputStream.mOutputStream).toByteArray();
3604             // Write EXIF chunk data
3605             dataOutputStream.write(exifBytes);
3606             // Write EXIF chunk CRC
3607             CRC32 crc = new CRC32();
3608             crc.update(exifBytes, 4 /* skip length bytes */, exifBytes.length - 4);
3609             dataOutputStream.writeInt((int) crc.getValue());
3610         }
3611         // Copy the rest of the file
3612         Streams.copy(dataInputStream, dataOutputStream);
3613     }
3614 
3615     // Reads the given EXIF byte area and save its tag data into attributes.
readExifSegment(byte[] exifBytes, int imageType)3616     private void readExifSegment(byte[] exifBytes, int imageType) throws IOException {
3617         ByteOrderedDataInputStream dataInputStream =
3618                 new ByteOrderedDataInputStream(exifBytes);
3619 
3620         // Parse TIFF Headers. See JEITA CP-3451C Section 4.5.2. Table 1.
3621         parseTiffHeaders(dataInputStream, exifBytes.length);
3622 
3623         // Read TIFF image file directories. See JEITA CP-3451C Section 4.5.2. Figure 6.
3624         readImageFileDirectory(dataInputStream, imageType);
3625     }
3626 
addDefaultValuesForCompatibility()3627     private void addDefaultValuesForCompatibility() {
3628         // If DATETIME tag has no value, then set the value to DATETIME_ORIGINAL tag's.
3629         String valueOfDateTimeOriginal = getAttribute(TAG_DATETIME_ORIGINAL);
3630         if (valueOfDateTimeOriginal != null && getAttribute(TAG_DATETIME) == null) {
3631             mAttributes[IFD_TYPE_PRIMARY].put(TAG_DATETIME,
3632                     ExifAttribute.createString(valueOfDateTimeOriginal));
3633         }
3634 
3635         // Add the default value.
3636         if (getAttribute(TAG_IMAGE_WIDTH) == null) {
3637             mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH,
3638                     ExifAttribute.createULong(0, mExifByteOrder));
3639         }
3640         if (getAttribute(TAG_IMAGE_LENGTH) == null) {
3641             mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH,
3642                     ExifAttribute.createULong(0, mExifByteOrder));
3643         }
3644         if (getAttribute(TAG_ORIENTATION) == null) {
3645             mAttributes[IFD_TYPE_PRIMARY].put(TAG_ORIENTATION,
3646                     ExifAttribute.createUShort(0, mExifByteOrder));
3647         }
3648         if (getAttribute(TAG_LIGHT_SOURCE) == null) {
3649             mAttributes[IFD_TYPE_EXIF].put(TAG_LIGHT_SOURCE,
3650                     ExifAttribute.createULong(0, mExifByteOrder));
3651         }
3652     }
3653 
readByteOrder(ByteOrderedDataInputStream dataInputStream)3654     private ByteOrder readByteOrder(ByteOrderedDataInputStream dataInputStream)
3655             throws IOException {
3656         // Read byte order.
3657         short byteOrder = dataInputStream.readShort();
3658         switch (byteOrder) {
3659             case BYTE_ALIGN_II:
3660                 if (DEBUG) {
3661                     Log.d(TAG, "readExifSegment: Byte Align II");
3662                 }
3663                 return ByteOrder.LITTLE_ENDIAN;
3664             case BYTE_ALIGN_MM:
3665                 if (DEBUG) {
3666                     Log.d(TAG, "readExifSegment: Byte Align MM");
3667                 }
3668                 return ByteOrder.BIG_ENDIAN;
3669             default:
3670                 throw new IOException("Invalid byte order: " + Integer.toHexString(byteOrder));
3671         }
3672     }
3673 
parseTiffHeaders(ByteOrderedDataInputStream dataInputStream, int exifBytesLength)3674     private void parseTiffHeaders(ByteOrderedDataInputStream dataInputStream,
3675             int exifBytesLength) throws IOException {
3676         // Read byte order
3677         mExifByteOrder = readByteOrder(dataInputStream);
3678         // Set byte order
3679         dataInputStream.setByteOrder(mExifByteOrder);
3680 
3681         // Check start code
3682         int startCode = dataInputStream.readUnsignedShort();
3683         if (mMimeType != IMAGE_TYPE_ORF && mMimeType != IMAGE_TYPE_RW2 && startCode != START_CODE) {
3684             throw new IOException("Invalid start code: " + Integer.toHexString(startCode));
3685         }
3686 
3687         // Read and skip to first ifd offset
3688         int firstIfdOffset = dataInputStream.readInt();
3689         if (firstIfdOffset < 8 || firstIfdOffset >= exifBytesLength) {
3690             throw new IOException("Invalid first Ifd offset: " + firstIfdOffset);
3691         }
3692         firstIfdOffset -= 8;
3693         if (firstIfdOffset > 0) {
3694             if (dataInputStream.skipBytes(firstIfdOffset) != firstIfdOffset) {
3695                 throw new IOException("Couldn't jump to first Ifd: " + firstIfdOffset);
3696             }
3697         }
3698     }
3699 
3700     // Reads image file directory, which is a tag group in EXIF.
readImageFileDirectory(ByteOrderedDataInputStream dataInputStream, @IfdType int ifdType)3701     private void readImageFileDirectory(ByteOrderedDataInputStream dataInputStream,
3702             @IfdType int ifdType) throws IOException {
3703         // Save offset of current IFD to prevent reading an IFD that is already read.
3704         mHandledIfdOffsets.add(dataInputStream.mPosition);
3705 
3706         if (dataInputStream.mPosition + 2 > dataInputStream.mLength) {
3707             // Return if there is no data from the offset.
3708             return;
3709         }
3710         // See TIFF 6.0 Section 2: TIFF Structure, Figure 1.
3711         short numberOfDirectoryEntry = dataInputStream.readShort();
3712         if (dataInputStream.mPosition + 12 * numberOfDirectoryEntry > dataInputStream.mLength
3713                 || numberOfDirectoryEntry <= 0) {
3714             // Return if the size of entries is either too big or negative.
3715             return;
3716         }
3717 
3718         if (DEBUG) {
3719             Log.d(TAG, "numberOfDirectoryEntry: " + numberOfDirectoryEntry);
3720         }
3721 
3722         // See TIFF 6.0 Section 2: TIFF Structure, "Image File Directory".
3723         for (short i = 0; i < numberOfDirectoryEntry; ++i) {
3724             int tagNumber = dataInputStream.readUnsignedShort();
3725             int dataFormat = dataInputStream.readUnsignedShort();
3726             int numberOfComponents = dataInputStream.readInt();
3727             // Next four bytes is for data offset or value.
3728             long nextEntryOffset = dataInputStream.peek() + 4;
3729 
3730             // Look up a corresponding tag from tag number
3731             ExifTag tag = (ExifTag) sExifTagMapsForReading[ifdType].get(tagNumber);
3732 
3733             if (DEBUG) {
3734                 Log.d(TAG, String.format("ifdType: %d, tagNumber: %d, tagName: %s, dataFormat: %d, "
3735                         + "numberOfComponents: %d", ifdType, tagNumber,
3736                         tag != null ? tag.name : null, dataFormat, numberOfComponents));
3737             }
3738 
3739             long byteCount = 0;
3740             boolean valid = false;
3741             if (tag == null) {
3742                 if (DEBUG) {
3743                     Log.d(TAG, "Skip the tag entry since tag number is not defined: " + tagNumber);
3744                 }
3745             } else if (dataFormat <= 0 || dataFormat >= IFD_FORMAT_BYTES_PER_FORMAT.length) {
3746                 if (DEBUG) {
3747                     Log.d(TAG, "Skip the tag entry since data format is invalid: " + dataFormat);
3748                 }
3749             } else {
3750                 byteCount = (long) numberOfComponents * IFD_FORMAT_BYTES_PER_FORMAT[dataFormat];
3751                 if (byteCount < 0 || byteCount > Integer.MAX_VALUE) {
3752                     if (DEBUG) {
3753                         Log.d(TAG, "Skip the tag entry since the number of components is invalid: "
3754                                 + numberOfComponents);
3755                     }
3756                 } else {
3757                     valid = true;
3758                 }
3759             }
3760             if (!valid) {
3761                 dataInputStream.seek(nextEntryOffset);
3762                 continue;
3763             }
3764 
3765             // Read a value from data field or seek to the value offset which is stored in data
3766             // field if the size of the entry value is bigger than 4.
3767             if (byteCount > 4) {
3768                 int offset = dataInputStream.readInt();
3769                 if (DEBUG) {
3770                     Log.d(TAG, "seek to data offset: " + offset);
3771                 }
3772                 if (mMimeType == IMAGE_TYPE_ORF) {
3773                     if (tag.name == TAG_MAKER_NOTE) {
3774                         // Save offset value for reading thumbnail
3775                         mOrfMakerNoteOffset = offset;
3776                     } else if (ifdType == IFD_TYPE_ORF_MAKER_NOTE
3777                             && tag.name == TAG_ORF_THUMBNAIL_IMAGE) {
3778                         // Retrieve & update values for thumbnail offset and length values for ORF
3779                         mOrfThumbnailOffset = offset;
3780                         mOrfThumbnailLength = numberOfComponents;
3781 
3782                         ExifAttribute compressionAttribute =
3783                                 ExifAttribute.createUShort(DATA_JPEG, mExifByteOrder);
3784                         ExifAttribute jpegInterchangeFormatAttribute =
3785                                 ExifAttribute.createULong(mOrfThumbnailOffset, mExifByteOrder);
3786                         ExifAttribute jpegInterchangeFormatLengthAttribute =
3787                                 ExifAttribute.createULong(mOrfThumbnailLength, mExifByteOrder);
3788 
3789                         mAttributes[IFD_TYPE_THUMBNAIL].put(TAG_COMPRESSION, compressionAttribute);
3790                         mAttributes[IFD_TYPE_THUMBNAIL].put(TAG_JPEG_INTERCHANGE_FORMAT,
3791                                 jpegInterchangeFormatAttribute);
3792                         mAttributes[IFD_TYPE_THUMBNAIL].put(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
3793                                 jpegInterchangeFormatLengthAttribute);
3794                     }
3795                 } else if (mMimeType == IMAGE_TYPE_RW2) {
3796                     if (tag.name == TAG_RW2_JPG_FROM_RAW) {
3797                         mRw2JpgFromRawOffset = offset;
3798                     }
3799                 }
3800                 if (offset + byteCount <= dataInputStream.mLength) {
3801                     dataInputStream.seek(offset);
3802                 } else {
3803                     // Skip if invalid data offset.
3804                     if (DEBUG) {
3805                         Log.d(TAG, "Skip the tag entry since data offset is invalid: " + offset);
3806                     }
3807                     dataInputStream.seek(nextEntryOffset);
3808                     continue;
3809                 }
3810             }
3811 
3812             // Recursively parse IFD when a IFD pointer tag appears.
3813             Integer nextIfdType = sExifPointerTagMap.get(tagNumber);
3814             if (DEBUG) {
3815                 Log.d(TAG, "nextIfdType: " + nextIfdType + " byteCount: " + byteCount);
3816             }
3817 
3818             if (nextIfdType != null) {
3819                 long offset = -1L;
3820                 // Get offset from data field
3821                 switch (dataFormat) {
3822                     case IFD_FORMAT_USHORT: {
3823                         offset = dataInputStream.readUnsignedShort();
3824                         break;
3825                     }
3826                     case IFD_FORMAT_SSHORT: {
3827                         offset = dataInputStream.readShort();
3828                         break;
3829                     }
3830                     case IFD_FORMAT_ULONG: {
3831                         offset = dataInputStream.readUnsignedInt();
3832                         break;
3833                     }
3834                     case IFD_FORMAT_SLONG:
3835                     case IFD_FORMAT_IFD: {
3836                         offset = dataInputStream.readInt();
3837                         break;
3838                     }
3839                     default: {
3840                         // Nothing to do
3841                         break;
3842                     }
3843                 }
3844                 if (DEBUG) {
3845                     Log.d(TAG, String.format("Offset: %d, tagName: %s", offset, tag.name));
3846                 }
3847 
3848                 // Check if the next IFD offset
3849                 // 1. Exists within the boundaries of the input stream
3850                 // 2. Does not point to a previously read IFD.
3851                 if (offset > 0L && offset < dataInputStream.mLength) {
3852                     if (!mHandledIfdOffsets.contains((int) offset)) {
3853                         dataInputStream.seek(offset);
3854                         readImageFileDirectory(dataInputStream, nextIfdType);
3855                     } else {
3856                         if (DEBUG) {
3857                             Log.d(TAG, "Skip jump into the IFD since it has already been read: "
3858                                     + "IfdType " + nextIfdType + " (at " + offset + ")");
3859                         }
3860                     }
3861                 } else {
3862                     if (DEBUG) {
3863                         Log.d(TAG, "Skip jump into the IFD since its offset is invalid: " + offset);
3864                     }
3865                 }
3866 
3867                 dataInputStream.seek(nextEntryOffset);
3868                 continue;
3869             }
3870 
3871             final int bytesOffset = dataInputStream.peek() + mExifOffset;
3872             final byte[] bytes = new byte[(int) byteCount];
3873             dataInputStream.readFully(bytes);
3874             ExifAttribute attribute = new ExifAttribute(dataFormat, numberOfComponents,
3875                     bytesOffset, bytes);
3876             mAttributes[ifdType].put(tag.name, attribute);
3877 
3878             // DNG files have a DNG Version tag specifying the version of specifications that the
3879             // image file is following.
3880             // See http://fileformats.archiveteam.org/wiki/DNG
3881             if (tag.name == TAG_DNG_VERSION) {
3882                 mMimeType = IMAGE_TYPE_DNG;
3883             }
3884 
3885             // PEF files have a Make or Model tag that begins with "PENTAX" or a compression tag
3886             // that is 65535.
3887             // See http://fileformats.archiveteam.org/wiki/Pentax_PEF
3888             if (((tag.name == TAG_MAKE || tag.name == TAG_MODEL)
3889                     && attribute.getStringValue(mExifByteOrder).contains(PEF_SIGNATURE))
3890                     || (tag.name == TAG_COMPRESSION
3891                     && attribute.getIntValue(mExifByteOrder) == 65535)) {
3892                 mMimeType = IMAGE_TYPE_PEF;
3893             }
3894 
3895             // Seek to next tag offset
3896             if (dataInputStream.peek() != nextEntryOffset) {
3897                 dataInputStream.seek(nextEntryOffset);
3898             }
3899         }
3900 
3901         if (dataInputStream.peek() + 4 <= dataInputStream.mLength) {
3902             int nextIfdOffset = dataInputStream.readInt();
3903             if (DEBUG) {
3904                 Log.d(TAG, String.format("nextIfdOffset: %d", nextIfdOffset));
3905             }
3906             // Check if the next IFD offset
3907             // 1. Exists within the boundaries of the input stream
3908             // 2. Does not point to a previously read IFD.
3909             if (nextIfdOffset > 0L && nextIfdOffset < dataInputStream.mLength) {
3910                 if (!mHandledIfdOffsets.contains(nextIfdOffset)) {
3911                     dataInputStream.seek(nextIfdOffset);
3912                     // Do not overwrite thumbnail IFD data if it alreay exists.
3913                     if (mAttributes[IFD_TYPE_THUMBNAIL].isEmpty()) {
3914                         readImageFileDirectory(dataInputStream, IFD_TYPE_THUMBNAIL);
3915                     } else if (mAttributes[IFD_TYPE_PREVIEW].isEmpty()) {
3916                         readImageFileDirectory(dataInputStream, IFD_TYPE_PREVIEW);
3917                     }
3918                 } else {
3919                     if (DEBUG) {
3920                         Log.d(TAG, "Stop reading file since re-reading an IFD may cause an "
3921                                 + "infinite loop: " + nextIfdOffset);
3922                     }
3923                 }
3924             } else {
3925                 if (DEBUG) {
3926                     Log.d(TAG, "Stop reading file since a wrong offset may cause an infinite loop: "
3927                             + nextIfdOffset);
3928                 }
3929             }
3930         }
3931     }
3932 
3933     /**
3934      * JPEG compressed images do not contain IMAGE_LENGTH & IMAGE_WIDTH tags.
3935      * This value uses JpegInterchangeFormat(JPEG data offset) value, and calls getJpegAttributes()
3936      * to locate SOF(Start of Frame) marker and update the image length & width values.
3937      * See JEITA CP-3451C Table 5 and Section 4.8.1. B.
3938      */
retrieveJpegImageSize(ByteOrderedDataInputStream in, int imageType)3939     private void retrieveJpegImageSize(ByteOrderedDataInputStream in, int imageType)
3940             throws IOException {
3941         // Check if image already has IMAGE_LENGTH & IMAGE_WIDTH values
3942         ExifAttribute imageLengthAttribute =
3943                 (ExifAttribute) mAttributes[imageType].get(TAG_IMAGE_LENGTH);
3944         ExifAttribute imageWidthAttribute =
3945                 (ExifAttribute) mAttributes[imageType].get(TAG_IMAGE_WIDTH);
3946 
3947         if (imageLengthAttribute == null || imageWidthAttribute == null) {
3948             // Find if offset for JPEG data exists
3949             ExifAttribute jpegInterchangeFormatAttribute =
3950                     (ExifAttribute) mAttributes[imageType].get(TAG_JPEG_INTERCHANGE_FORMAT);
3951             if (jpegInterchangeFormatAttribute != null) {
3952                 int jpegInterchangeFormat =
3953                         jpegInterchangeFormatAttribute.getIntValue(mExifByteOrder);
3954 
3955                 // Searches for SOF marker in JPEG data and updates IMAGE_LENGTH & IMAGE_WIDTH tags
3956                 getJpegAttributes(in, jpegInterchangeFormat, imageType);
3957             }
3958         }
3959     }
3960 
3961     // Sets thumbnail offset & length attributes based on JpegInterchangeFormat or StripOffsets tags
setThumbnailData(ByteOrderedDataInputStream in)3962     private void setThumbnailData(ByteOrderedDataInputStream in) throws IOException {
3963         HashMap thumbnailData = mAttributes[IFD_TYPE_THUMBNAIL];
3964 
3965         ExifAttribute compressionAttribute =
3966                 (ExifAttribute) thumbnailData.get(TAG_COMPRESSION);
3967         if (compressionAttribute != null) {
3968             mThumbnailCompression = compressionAttribute.getIntValue(mExifByteOrder);
3969             switch (mThumbnailCompression) {
3970                 case DATA_JPEG: {
3971                     handleThumbnailFromJfif(in, thumbnailData);
3972                     break;
3973                 }
3974                 case DATA_UNCOMPRESSED:
3975                 case DATA_JPEG_COMPRESSED: {
3976                     if (isSupportedDataType(thumbnailData)) {
3977                         handleThumbnailFromStrips(in, thumbnailData);
3978                     }
3979                     break;
3980                 }
3981             }
3982         } else {
3983             // Thumbnail data may not contain Compression tag value
3984             handleThumbnailFromJfif(in, thumbnailData);
3985         }
3986     }
3987 
3988     // Check JpegInterchangeFormat(JFIF) tags to retrieve thumbnail offset & length values
3989     // and reads the corresponding bytes if stream does not support seek function
handleThumbnailFromJfif(ByteOrderedDataInputStream in, HashMap thumbnailData)3990     private void handleThumbnailFromJfif(ByteOrderedDataInputStream in, HashMap thumbnailData)
3991             throws IOException {
3992         ExifAttribute jpegInterchangeFormatAttribute =
3993                 (ExifAttribute) thumbnailData.get(TAG_JPEG_INTERCHANGE_FORMAT);
3994         ExifAttribute jpegInterchangeFormatLengthAttribute =
3995                 (ExifAttribute) thumbnailData.get(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
3996         if (jpegInterchangeFormatAttribute != null
3997                 && jpegInterchangeFormatLengthAttribute != null) {
3998             int thumbnailOffset = jpegInterchangeFormatAttribute.getIntValue(mExifByteOrder);
3999             int thumbnailLength = jpegInterchangeFormatLengthAttribute.getIntValue(mExifByteOrder);
4000 
4001             if (mMimeType == IMAGE_TYPE_ORF) {
4002                 // Update offset value since RAF files have IFD data preceding MakerNote data.
4003                 thumbnailOffset += mOrfMakerNoteOffset;
4004             }
4005             // The following code limits the size of thumbnail size not to overflow EXIF data area.
4006             thumbnailLength = Math.min(thumbnailLength, in.getLength() - thumbnailOffset);
4007 
4008             if (thumbnailOffset > 0 && thumbnailLength > 0) {
4009                 mHasThumbnail = true;
4010                 // Need to add mExifOffset, which is the offset to the EXIF data segment
4011                 mThumbnailOffset = thumbnailOffset + mExifOffset;
4012                 mThumbnailLength = thumbnailLength;
4013                 mThumbnailCompression = DATA_JPEG;
4014 
4015                 if (mFilename == null && mAssetInputStream == null
4016                         && mSeekableFileDescriptor == null) {
4017                     // TODO: Need to handle potential OutOfMemoryError
4018                     // Save the thumbnail in memory if the input doesn't support reading again.
4019                     byte[] thumbnailBytes = new byte[mThumbnailLength];
4020                     in.seek(mThumbnailOffset);
4021                     in.readFully(thumbnailBytes);
4022                     mThumbnailBytes = thumbnailBytes;
4023                 }
4024             }
4025             if (DEBUG) {
4026                 Log.d(TAG, "Setting thumbnail attributes with offset: " + thumbnailOffset
4027                         + ", length: " + thumbnailLength);
4028             }
4029         }
4030     }
4031 
4032     // Check StripOffsets & StripByteCounts tags to retrieve thumbnail offset & length values
handleThumbnailFromStrips(ByteOrderedDataInputStream in, HashMap thumbnailData)4033     private void handleThumbnailFromStrips(ByteOrderedDataInputStream in, HashMap thumbnailData)
4034             throws IOException {
4035         ExifAttribute stripOffsetsAttribute =
4036                 (ExifAttribute) thumbnailData.get(TAG_STRIP_OFFSETS);
4037         ExifAttribute stripByteCountsAttribute =
4038                 (ExifAttribute) thumbnailData.get(TAG_STRIP_BYTE_COUNTS);
4039 
4040         if (stripOffsetsAttribute != null && stripByteCountsAttribute != null) {
4041             long[] stripOffsets =
4042                     convertToLongArray(stripOffsetsAttribute.getValue(mExifByteOrder));
4043             long[] stripByteCounts =
4044                     convertToLongArray(stripByteCountsAttribute.getValue(mExifByteOrder));
4045 
4046             if (stripOffsets == null || stripOffsets.length == 0) {
4047                 Log.w(TAG, "stripOffsets should not be null or have zero length.");
4048                 return;
4049             }
4050             if (stripByteCounts == null || stripByteCounts.length == 0) {
4051                 Log.w(TAG, "stripByteCounts should not be null or have zero length.");
4052                 return;
4053             }
4054             if (stripOffsets.length != stripByteCounts.length) {
4055                 Log.w(TAG, "stripOffsets and stripByteCounts should have same length.");
4056                 return;
4057             }
4058 
4059             // TODO: Need to handle potential OutOfMemoryError
4060             // Set thumbnail byte array data for non-consecutive strip bytes
4061             byte[] totalStripBytes =
4062                     new byte[(int) Arrays.stream(stripByteCounts).sum()];
4063 
4064             int bytesRead = 0;
4065             int bytesAdded = 0;
4066             mHasThumbnail = mHasThumbnailStrips = mAreThumbnailStripsConsecutive = true;
4067             for (int i = 0; i < stripOffsets.length; i++) {
4068                 int stripOffset = (int) stripOffsets[i];
4069                 int stripByteCount = (int) stripByteCounts[i];
4070 
4071                 // Check if strips are consecutive
4072                 // TODO: Add test for non-consecutive thumbnail image
4073                 if (i < stripOffsets.length - 1
4074                         && stripOffset + stripByteCount != stripOffsets[i + 1]) {
4075                     mAreThumbnailStripsConsecutive = false;
4076                 }
4077 
4078                 // Skip to offset
4079                 int skipBytes = stripOffset - bytesRead;
4080                 if (skipBytes < 0) {
4081                     Log.d(TAG, "Invalid strip offset value");
4082                 }
4083                 in.seek(skipBytes);
4084                 bytesRead += skipBytes;
4085 
4086                 // TODO: Need to handle potential OutOfMemoryError
4087                 // Read strip bytes
4088                 byte[] stripBytes = new byte[stripByteCount];
4089                 in.read(stripBytes);
4090                 bytesRead += stripByteCount;
4091 
4092                 // Add bytes to array
4093                 System.arraycopy(stripBytes, 0, totalStripBytes, bytesAdded,
4094                         stripBytes.length);
4095                 bytesAdded += stripBytes.length;
4096             }
4097             mThumbnailBytes = totalStripBytes;
4098 
4099             if (mAreThumbnailStripsConsecutive) {
4100                 // Need to add mExifOffset, which is the offset to the EXIF data segment
4101                 mThumbnailOffset = (int) stripOffsets[0] + mExifOffset;
4102                 mThumbnailLength = totalStripBytes.length;
4103             }
4104         }
4105     }
4106 
4107     // Check if thumbnail data type is currently supported or not
isSupportedDataType(HashMap thumbnailData)4108     private boolean isSupportedDataType(HashMap thumbnailData) throws IOException {
4109         ExifAttribute bitsPerSampleAttribute =
4110                 (ExifAttribute) thumbnailData.get(TAG_BITS_PER_SAMPLE);
4111         if (bitsPerSampleAttribute != null) {
4112             int[] bitsPerSampleValue = (int[]) bitsPerSampleAttribute.getValue(mExifByteOrder);
4113 
4114             if (Arrays.equals(BITS_PER_SAMPLE_RGB, bitsPerSampleValue)) {
4115                 return true;
4116             }
4117 
4118             // See DNG Specification 1.4.0.0. Section 3, Compression.
4119             if (mMimeType == IMAGE_TYPE_DNG) {
4120                 ExifAttribute photometricInterpretationAttribute =
4121                         (ExifAttribute) thumbnailData.get(TAG_PHOTOMETRIC_INTERPRETATION);
4122                 if (photometricInterpretationAttribute != null) {
4123                     int photometricInterpretationValue
4124                             = photometricInterpretationAttribute.getIntValue(mExifByteOrder);
4125                     if ((photometricInterpretationValue == PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO
4126                             && Arrays.equals(bitsPerSampleValue, BITS_PER_SAMPLE_GREYSCALE_2))
4127                             || ((photometricInterpretationValue == PHOTOMETRIC_INTERPRETATION_YCBCR)
4128                             && (Arrays.equals(bitsPerSampleValue, BITS_PER_SAMPLE_RGB)))) {
4129                         return true;
4130                     } else {
4131                         // TODO: Add support for lossless Huffman JPEG data
4132                     }
4133                 }
4134             }
4135         }
4136         if (DEBUG) {
4137             Log.d(TAG, "Unsupported data type value");
4138         }
4139         return false;
4140     }
4141 
4142     // Returns true if the image length and width values are <= 512.
4143     // See Section 4.8 of http://standardsproposals.bsigroup.com/Home/getPDF/567
isThumbnail(HashMap map)4144     private boolean isThumbnail(HashMap map) throws IOException {
4145         ExifAttribute imageLengthAttribute = (ExifAttribute) map.get(TAG_IMAGE_LENGTH);
4146         ExifAttribute imageWidthAttribute = (ExifAttribute) map.get(TAG_IMAGE_WIDTH);
4147 
4148         if (imageLengthAttribute != null && imageWidthAttribute != null) {
4149             int imageLengthValue = imageLengthAttribute.getIntValue(mExifByteOrder);
4150             int imageWidthValue = imageWidthAttribute.getIntValue(mExifByteOrder);
4151             if (imageLengthValue <= MAX_THUMBNAIL_SIZE && imageWidthValue <= MAX_THUMBNAIL_SIZE) {
4152                 return true;
4153             }
4154         }
4155         return false;
4156     }
4157 
4158     // Validate primary, preview, thumbnail image data by comparing image size
validateImages()4159     private void validateImages() throws IOException {
4160         // Swap images based on size (primary > preview > thumbnail)
4161         swapBasedOnImageSize(IFD_TYPE_PRIMARY, IFD_TYPE_PREVIEW);
4162         swapBasedOnImageSize(IFD_TYPE_PRIMARY, IFD_TYPE_THUMBNAIL);
4163         swapBasedOnImageSize(IFD_TYPE_PREVIEW, IFD_TYPE_THUMBNAIL);
4164 
4165         // TODO (b/142296453): Revise image width/height setting logic
4166         // Check if image has PixelXDimension/PixelYDimension tags, which contain valid image
4167         // sizes, excluding padding at the right end or bottom end of the image to make sure that
4168         // the values are multiples of 64. See JEITA CP-3451C Table 5 and Section 4.8.1. B.
4169         ExifAttribute pixelXDimAttribute =
4170                 (ExifAttribute) mAttributes[IFD_TYPE_EXIF].get(TAG_PIXEL_X_DIMENSION);
4171         ExifAttribute pixelYDimAttribute =
4172                 (ExifAttribute) mAttributes[IFD_TYPE_EXIF].get(TAG_PIXEL_Y_DIMENSION);
4173         if (pixelXDimAttribute != null && pixelYDimAttribute != null) {
4174             mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH, pixelXDimAttribute);
4175             mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH, pixelYDimAttribute);
4176         }
4177 
4178         // Check whether thumbnail image exists and whether preview image satisfies the thumbnail
4179         // image requirements
4180         if (mAttributes[IFD_TYPE_THUMBNAIL].isEmpty()) {
4181             if (isThumbnail(mAttributes[IFD_TYPE_PREVIEW])) {
4182                 mAttributes[IFD_TYPE_THUMBNAIL] = mAttributes[IFD_TYPE_PREVIEW];
4183                 mAttributes[IFD_TYPE_PREVIEW] = new HashMap();
4184             }
4185         }
4186 
4187         // Check if the thumbnail image satisfies the thumbnail size requirements
4188         if (!isThumbnail(mAttributes[IFD_TYPE_THUMBNAIL])) {
4189             Log.d(TAG, "No image meets the size requirements of a thumbnail image.");
4190         }
4191     }
4192 
4193     /**
4194      * If image is uncompressed, ImageWidth/Length tags are used to store size info.
4195      * However, uncompressed images often store extra pixels around the edges of the final image,
4196      * which results in larger values for TAG_IMAGE_WIDTH and TAG_IMAGE_LENGTH tags.
4197      * This method corrects those tag values by checking first the values of TAG_DEFAULT_CROP_SIZE
4198      * See DNG Specification 1.4.0.0. Section 4. (DefaultCropSize)
4199      *
4200      * If image is a RW2 file, valid image sizes are stored in SensorBorder tags.
4201      * See tiff_parser.cc GetFullDimension32()
4202      * */
updateImageSizeValues(ByteOrderedDataInputStream in, int imageType)4203     private void updateImageSizeValues(ByteOrderedDataInputStream in, int imageType)
4204             throws IOException {
4205         // Uncompressed image valid image size values
4206         ExifAttribute defaultCropSizeAttribute =
4207                 (ExifAttribute) mAttributes[imageType].get(TAG_DEFAULT_CROP_SIZE);
4208         // RW2 image valid image size values
4209         ExifAttribute topBorderAttribute =
4210                 (ExifAttribute) mAttributes[imageType].get(TAG_RW2_SENSOR_TOP_BORDER);
4211         ExifAttribute leftBorderAttribute =
4212                 (ExifAttribute) mAttributes[imageType].get(TAG_RW2_SENSOR_LEFT_BORDER);
4213         ExifAttribute bottomBorderAttribute =
4214                 (ExifAttribute) mAttributes[imageType].get(TAG_RW2_SENSOR_BOTTOM_BORDER);
4215         ExifAttribute rightBorderAttribute =
4216                 (ExifAttribute) mAttributes[imageType].get(TAG_RW2_SENSOR_RIGHT_BORDER);
4217 
4218         if (defaultCropSizeAttribute != null) {
4219             // Update for uncompressed image
4220             ExifAttribute defaultCropSizeXAttribute, defaultCropSizeYAttribute;
4221             if (defaultCropSizeAttribute.format == IFD_FORMAT_URATIONAL) {
4222                 Rational[] defaultCropSizeValue =
4223                         (Rational[]) defaultCropSizeAttribute.getValue(mExifByteOrder);
4224                 defaultCropSizeXAttribute =
4225                         ExifAttribute.createURational(defaultCropSizeValue[0], mExifByteOrder);
4226                 defaultCropSizeYAttribute =
4227                         ExifAttribute.createURational(defaultCropSizeValue[1], mExifByteOrder);
4228             } else {
4229                 int[] defaultCropSizeValue =
4230                         (int[]) defaultCropSizeAttribute.getValue(mExifByteOrder);
4231                 defaultCropSizeXAttribute =
4232                         ExifAttribute.createUShort(defaultCropSizeValue[0], mExifByteOrder);
4233                 defaultCropSizeYAttribute =
4234                         ExifAttribute.createUShort(defaultCropSizeValue[1], mExifByteOrder);
4235             }
4236             mAttributes[imageType].put(TAG_IMAGE_WIDTH, defaultCropSizeXAttribute);
4237             mAttributes[imageType].put(TAG_IMAGE_LENGTH, defaultCropSizeYAttribute);
4238         } else if (topBorderAttribute != null && leftBorderAttribute != null &&
4239                 bottomBorderAttribute != null && rightBorderAttribute != null) {
4240             // Update for RW2 image
4241             int topBorderValue = topBorderAttribute.getIntValue(mExifByteOrder);
4242             int bottomBorderValue = bottomBorderAttribute.getIntValue(mExifByteOrder);
4243             int rightBorderValue = rightBorderAttribute.getIntValue(mExifByteOrder);
4244             int leftBorderValue = leftBorderAttribute.getIntValue(mExifByteOrder);
4245             if (bottomBorderValue > topBorderValue && rightBorderValue > leftBorderValue) {
4246                 int length = bottomBorderValue - topBorderValue;
4247                 int width = rightBorderValue - leftBorderValue;
4248                 ExifAttribute imageLengthAttribute =
4249                         ExifAttribute.createUShort(length, mExifByteOrder);
4250                 ExifAttribute imageWidthAttribute =
4251                         ExifAttribute.createUShort(width, mExifByteOrder);
4252                 mAttributes[imageType].put(TAG_IMAGE_LENGTH, imageLengthAttribute);
4253                 mAttributes[imageType].put(TAG_IMAGE_WIDTH, imageWidthAttribute);
4254             }
4255         } else {
4256             retrieveJpegImageSize(in, imageType);
4257         }
4258     }
4259 
4260     // Writes an Exif segment into the given output stream.
writeExifSegment(ByteOrderedDataOutputStream dataOutputStream)4261     private int writeExifSegment(ByteOrderedDataOutputStream dataOutputStream) throws IOException {
4262         // The following variables are for calculating each IFD tag group size in bytes.
4263         int[] ifdOffsets = new int[EXIF_TAGS.length];
4264         int[] ifdDataSizes = new int[EXIF_TAGS.length];
4265 
4266         // Remove IFD pointer tags (we'll re-add it later.)
4267         for (ExifTag tag : EXIF_POINTER_TAGS) {
4268             removeAttribute(tag.name);
4269         }
4270         // Remove old thumbnail data
4271         removeAttribute(JPEG_INTERCHANGE_FORMAT_TAG.name);
4272         removeAttribute(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name);
4273 
4274         // Remove null value tags.
4275         for (int ifdType = 0; ifdType < EXIF_TAGS.length; ++ifdType) {
4276             for (Object obj : mAttributes[ifdType].entrySet().toArray()) {
4277                 final Map.Entry entry = (Map.Entry) obj;
4278                 if (entry.getValue() == null) {
4279                     mAttributes[ifdType].remove(entry.getKey());
4280                 }
4281             }
4282         }
4283 
4284         // Add IFD pointer tags. The next offset of primary image TIFF IFD will have thumbnail IFD
4285         // offset when there is one or more tags in the thumbnail IFD.
4286         if (!mAttributes[IFD_TYPE_EXIF].isEmpty()) {
4287             mAttributes[IFD_TYPE_PRIMARY].put(EXIF_POINTER_TAGS[1].name,
4288                     ExifAttribute.createULong(0, mExifByteOrder));
4289         }
4290         if (!mAttributes[IFD_TYPE_GPS].isEmpty()) {
4291             mAttributes[IFD_TYPE_PRIMARY].put(EXIF_POINTER_TAGS[2].name,
4292                     ExifAttribute.createULong(0, mExifByteOrder));
4293         }
4294         if (!mAttributes[IFD_TYPE_INTEROPERABILITY].isEmpty()) {
4295             mAttributes[IFD_TYPE_EXIF].put(EXIF_POINTER_TAGS[3].name,
4296                     ExifAttribute.createULong(0, mExifByteOrder));
4297         }
4298         if (mHasThumbnail) {
4299             mAttributes[IFD_TYPE_THUMBNAIL].put(JPEG_INTERCHANGE_FORMAT_TAG.name,
4300                     ExifAttribute.createULong(0, mExifByteOrder));
4301             mAttributes[IFD_TYPE_THUMBNAIL].put(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name,
4302                     ExifAttribute.createULong(mThumbnailLength, mExifByteOrder));
4303         }
4304 
4305         // Calculate IFD group data area sizes. IFD group data area is assigned to save the entry
4306         // value which has a bigger size than 4 bytes.
4307         for (int i = 0; i < EXIF_TAGS.length; ++i) {
4308             int sum = 0;
4309             for (Map.Entry entry : (Set<Map.Entry>) mAttributes[i].entrySet()) {
4310                 final ExifAttribute exifAttribute = (ExifAttribute) entry.getValue();
4311                 final int size = exifAttribute.size();
4312                 if (size > 4) {
4313                     sum += size;
4314                 }
4315             }
4316             ifdDataSizes[i] += sum;
4317         }
4318 
4319         // Calculate IFD offsets.
4320         // 8 bytes are for TIFF headers: 2 bytes (byte order) + 2 bytes (identifier) + 4 bytes
4321         // (offset of IFDs)
4322         int position = 8;
4323         for (int ifdType = 0; ifdType < EXIF_TAGS.length; ++ifdType) {
4324             if (!mAttributes[ifdType].isEmpty()) {
4325                 ifdOffsets[ifdType] = position;
4326                 position += 2 + mAttributes[ifdType].size() * 12 + 4 + ifdDataSizes[ifdType];
4327             }
4328         }
4329         if (mHasThumbnail) {
4330             int thumbnailOffset = position;
4331             mAttributes[IFD_TYPE_THUMBNAIL].put(JPEG_INTERCHANGE_FORMAT_TAG.name,
4332                     ExifAttribute.createULong(thumbnailOffset, mExifByteOrder));
4333             // Need to add mExifOffset, which is the offset to the EXIF data segment
4334             mThumbnailOffset = thumbnailOffset + mExifOffset;
4335             position += mThumbnailLength;
4336         }
4337 
4338         int totalSize = position;
4339         if (mMimeType == IMAGE_TYPE_JPEG) {
4340             // Add 8 bytes for APP1 size and identifier data
4341             totalSize += 8;
4342         }
4343         if (DEBUG) {
4344             for (int i = 0; i < EXIF_TAGS.length; ++i) {
4345                 Log.d(TAG, String.format("index: %d, offsets: %d, tag count: %d, data sizes: %d, "
4346                                 + "total size: %d", i, ifdOffsets[i], mAttributes[i].size(),
4347                         ifdDataSizes[i], totalSize));
4348             }
4349         }
4350 
4351         // Update IFD pointer tags with the calculated offsets.
4352         if (!mAttributes[IFD_TYPE_EXIF].isEmpty()) {
4353             mAttributes[IFD_TYPE_PRIMARY].put(EXIF_POINTER_TAGS[1].name,
4354                     ExifAttribute.createULong(ifdOffsets[IFD_TYPE_EXIF], mExifByteOrder));
4355         }
4356         if (!mAttributes[IFD_TYPE_GPS].isEmpty()) {
4357             mAttributes[IFD_TYPE_PRIMARY].put(EXIF_POINTER_TAGS[2].name,
4358                     ExifAttribute.createULong(ifdOffsets[IFD_TYPE_GPS], mExifByteOrder));
4359         }
4360         if (!mAttributes[IFD_TYPE_INTEROPERABILITY].isEmpty()) {
4361             mAttributes[IFD_TYPE_EXIF].put(EXIF_POINTER_TAGS[3].name, ExifAttribute.createULong(
4362                     ifdOffsets[IFD_TYPE_INTEROPERABILITY], mExifByteOrder));
4363         }
4364 
4365         if (mMimeType == IMAGE_TYPE_JPEG) {
4366             // Write JPEG specific data (APP1 size, APP1 identifier)
4367             dataOutputStream.writeUnsignedShort(totalSize);
4368             dataOutputStream.write(IDENTIFIER_EXIF_APP1);
4369         } else if (mMimeType == IMAGE_TYPE_PNG) {
4370             // Write PNG specific data (chunk size, chunk type)
4371             dataOutputStream.writeInt(totalSize);
4372             dataOutputStream.write(PNG_CHUNK_TYPE_EXIF);
4373         }
4374 
4375         // Write TIFF Headers. See JEITA CP-3451C Section 4.5.2. Table 1.
4376         dataOutputStream.writeShort(mExifByteOrder == ByteOrder.BIG_ENDIAN
4377                 ? BYTE_ALIGN_MM : BYTE_ALIGN_II);
4378         dataOutputStream.setByteOrder(mExifByteOrder);
4379         dataOutputStream.writeUnsignedShort(START_CODE);
4380         dataOutputStream.writeUnsignedInt(IFD_OFFSET);
4381 
4382         // Write IFD groups. See JEITA CP-3451C Section 4.5.8. Figure 9.
4383         for (int ifdType = 0; ifdType < EXIF_TAGS.length; ++ifdType) {
4384             if (!mAttributes[ifdType].isEmpty()) {
4385                 // See JEITA CP-3451C Section 4.6.2: IFD structure.
4386                 // Write entry count
4387                 dataOutputStream.writeUnsignedShort(mAttributes[ifdType].size());
4388 
4389                 // Write entry info
4390                 int dataOffset = ifdOffsets[ifdType] + 2 + mAttributes[ifdType].size() * 12 + 4;
4391                 for (Map.Entry entry : (Set<Map.Entry>) mAttributes[ifdType].entrySet()) {
4392                     // Convert tag name to tag number.
4393                     final ExifTag tag =
4394                             (ExifTag) sExifTagMapsForWriting[ifdType].get(entry.getKey());
4395                     final int tagNumber = tag.number;
4396                     final ExifAttribute attribute = (ExifAttribute) entry.getValue();
4397                     final int size = attribute.size();
4398 
4399                     dataOutputStream.writeUnsignedShort(tagNumber);
4400                     dataOutputStream.writeUnsignedShort(attribute.format);
4401                     dataOutputStream.writeInt(attribute.numberOfComponents);
4402                     if (size > 4) {
4403                         dataOutputStream.writeUnsignedInt(dataOffset);
4404                         dataOffset += size;
4405                     } else {
4406                         dataOutputStream.write(attribute.bytes);
4407                         // Fill zero up to 4 bytes
4408                         if (size < 4) {
4409                             for (int i = size; i < 4; ++i) {
4410                                 dataOutputStream.writeByte(0);
4411                             }
4412                         }
4413                     }
4414                 }
4415 
4416                 // Write the next offset. It writes the offset of thumbnail IFD if there is one or
4417                 // more tags in the thumbnail IFD when the current IFD is the primary image TIFF
4418                 // IFD; Otherwise 0.
4419                 if (ifdType == 0 && !mAttributes[IFD_TYPE_THUMBNAIL].isEmpty()) {
4420                     dataOutputStream.writeUnsignedInt(ifdOffsets[IFD_TYPE_THUMBNAIL]);
4421                 } else {
4422                     dataOutputStream.writeUnsignedInt(0);
4423                 }
4424 
4425                 // Write values of data field exceeding 4 bytes after the next offset.
4426                 for (Map.Entry entry : (Set<Map.Entry>) mAttributes[ifdType].entrySet()) {
4427                     ExifAttribute attribute = (ExifAttribute) entry.getValue();
4428 
4429                     if (attribute.bytes.length > 4) {
4430                         dataOutputStream.write(attribute.bytes, 0, attribute.bytes.length);
4431                     }
4432                 }
4433             }
4434         }
4435 
4436         // Write thumbnail
4437         if (mHasThumbnail) {
4438             dataOutputStream.write(getThumbnailBytes());
4439         }
4440 
4441         // Reset the byte order to big endian in order to write remaining parts of the JPEG file.
4442         dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN);
4443 
4444         return totalSize;
4445     }
4446 
4447     /**
4448      * Determines the data format of EXIF entry value.
4449      *
4450      * @param entryValue The value to be determined.
4451      * @return Returns two data formats guessed as a pair in integer. If there is no two candidate
4452                data formats for the given entry value, returns {@code -1} in the second of the pair.
4453      */
guessDataFormat(String entryValue)4454     private static Pair<Integer, Integer> guessDataFormat(String entryValue) {
4455         // See TIFF 6.0 Section 2, "Image File Directory".
4456         // Take the first component if there are more than one component.
4457         if (entryValue.contains(",")) {
4458             String[] entryValues = entryValue.split(",");
4459             Pair<Integer, Integer> dataFormat = guessDataFormat(entryValues[0]);
4460             if (dataFormat.first == IFD_FORMAT_STRING) {
4461                 return dataFormat;
4462             }
4463             for (int i = 1; i < entryValues.length; ++i) {
4464                 final Pair<Integer, Integer> guessDataFormat = guessDataFormat(entryValues[i]);
4465                 int first = -1, second = -1;
4466                 if (guessDataFormat.first == dataFormat.first
4467                         || guessDataFormat.second == dataFormat.first) {
4468                     first = dataFormat.first;
4469                 }
4470                 if (dataFormat.second != -1 && (guessDataFormat.first == dataFormat.second
4471                         || guessDataFormat.second == dataFormat.second)) {
4472                     second = dataFormat.second;
4473                 }
4474                 if (first == -1 && second == -1) {
4475                     return new Pair<>(IFD_FORMAT_STRING, -1);
4476                 }
4477                 if (first == -1) {
4478                     dataFormat = new Pair<>(second, -1);
4479                     continue;
4480                 }
4481                 if (second == -1) {
4482                     dataFormat = new Pair<>(first, -1);
4483                     continue;
4484                 }
4485             }
4486             return dataFormat;
4487         }
4488 
4489         if (entryValue.contains("/")) {
4490             String[] rationalNumber = entryValue.split("/");
4491             if (rationalNumber.length == 2) {
4492                 try {
4493                     long numerator = (long) Double.parseDouble(rationalNumber[0]);
4494                     long denominator = (long) Double.parseDouble(rationalNumber[1]);
4495                     if (numerator < 0L || denominator < 0L) {
4496                         return new Pair<>(IFD_FORMAT_SRATIONAL, -1);
4497                     }
4498                     if (numerator > Integer.MAX_VALUE || denominator > Integer.MAX_VALUE) {
4499                         return new Pair<>(IFD_FORMAT_URATIONAL, -1);
4500                     }
4501                     return new Pair<>(IFD_FORMAT_SRATIONAL, IFD_FORMAT_URATIONAL);
4502                 } catch (NumberFormatException e)  {
4503                     // Ignored
4504                 }
4505             }
4506             return new Pair<>(IFD_FORMAT_STRING, -1);
4507         }
4508         try {
4509             Long longValue = Long.parseLong(entryValue);
4510             if (longValue >= 0 && longValue <= 65535) {
4511                 return new Pair<>(IFD_FORMAT_USHORT, IFD_FORMAT_ULONG);
4512             }
4513             if (longValue < 0) {
4514                 return new Pair<>(IFD_FORMAT_SLONG, -1);
4515             }
4516             return new Pair<>(IFD_FORMAT_ULONG, -1);
4517         } catch (NumberFormatException e) {
4518             // Ignored
4519         }
4520         try {
4521             Double.parseDouble(entryValue);
4522             return new Pair<>(IFD_FORMAT_DOUBLE, -1);
4523         } catch (NumberFormatException e) {
4524             // Ignored
4525         }
4526         return new Pair<>(IFD_FORMAT_STRING, -1);
4527     }
4528 
4529     // An input stream to parse EXIF data area, which can be written in either little or big endian
4530     // order.
4531     private static class ByteOrderedDataInputStream extends InputStream implements DataInput {
4532         private static final ByteOrder LITTLE_ENDIAN = ByteOrder.LITTLE_ENDIAN;
4533         private static final ByteOrder BIG_ENDIAN = ByteOrder.BIG_ENDIAN;
4534 
4535         private DataInputStream mDataInputStream;
4536         private InputStream mInputStream;
4537         private ByteOrder mByteOrder = ByteOrder.BIG_ENDIAN;
4538         private final int mLength;
4539         private int mPosition;
4540 
ByteOrderedDataInputStream(InputStream in)4541         public ByteOrderedDataInputStream(InputStream in) throws IOException {
4542             mInputStream = in;
4543             mDataInputStream = new DataInputStream(in);
4544             mLength = mDataInputStream.available();
4545             mPosition = 0;
4546             // TODO (b/142218289): Need to handle case where input stream does not support mark
4547             mDataInputStream.mark(mLength);
4548         }
4549 
ByteOrderedDataInputStream(byte[] bytes)4550         public ByteOrderedDataInputStream(byte[] bytes) throws IOException {
4551             this(new ByteArrayInputStream(bytes));
4552         }
4553 
setByteOrder(ByteOrder byteOrder)4554         public void setByteOrder(ByteOrder byteOrder) {
4555             mByteOrder = byteOrder;
4556         }
4557 
seek(long byteCount)4558         public void seek(long byteCount) throws IOException {
4559             if (mPosition > byteCount) {
4560                 mPosition = 0;
4561                 mDataInputStream.reset();
4562                 // TODO (b/142218289): Need to handle case where input stream does not support mark
4563                 mDataInputStream.mark(mLength);
4564             } else {
4565                 byteCount -= mPosition;
4566             }
4567 
4568             if (skipBytes((int) byteCount) != (int) byteCount) {
4569                 throw new IOException("Couldn't seek up to the byteCount");
4570             }
4571         }
4572 
peek()4573         public int peek() {
4574             return mPosition;
4575         }
4576 
4577         @Override
available()4578         public int available() throws IOException {
4579             return mDataInputStream.available();
4580         }
4581 
4582         @Override
read()4583         public int read() throws IOException {
4584             ++mPosition;
4585             return mDataInputStream.read();
4586         }
4587 
4588         @Override
readUnsignedByte()4589         public int readUnsignedByte() throws IOException {
4590             ++mPosition;
4591             return mDataInputStream.readUnsignedByte();
4592         }
4593 
4594         @Override
readLine()4595         public String readLine() throws IOException {
4596             Log.d(TAG, "Currently unsupported");
4597             return null;
4598         }
4599 
4600         @Override
readBoolean()4601         public boolean readBoolean() throws IOException {
4602             ++mPosition;
4603             return mDataInputStream.readBoolean();
4604         }
4605 
4606         @Override
readChar()4607         public char readChar() throws IOException {
4608             mPosition += 2;
4609             return mDataInputStream.readChar();
4610         }
4611 
4612         @Override
readUTF()4613         public String readUTF() throws IOException {
4614             mPosition += 2;
4615             return mDataInputStream.readUTF();
4616         }
4617 
4618         @Override
readFully(byte[] buffer, int offset, int length)4619         public void readFully(byte[] buffer, int offset, int length) throws IOException {
4620             mPosition += length;
4621             if (mPosition > mLength) {
4622                 throw new EOFException();
4623             }
4624             if (mDataInputStream.read(buffer, offset, length) != length) {
4625                 throw new IOException("Couldn't read up to the length of buffer");
4626             }
4627         }
4628 
4629         @Override
readFully(byte[] buffer)4630         public void readFully(byte[] buffer) throws IOException {
4631             mPosition += buffer.length;
4632             if (mPosition > mLength) {
4633                 throw new EOFException();
4634             }
4635             if (mDataInputStream.read(buffer, 0, buffer.length) != buffer.length) {
4636                 throw new IOException("Couldn't read up to the length of buffer");
4637             }
4638         }
4639 
4640         @Override
readByte()4641         public byte readByte() throws IOException {
4642             ++mPosition;
4643             if (mPosition > mLength) {
4644                 throw new EOFException();
4645             }
4646             int ch = mDataInputStream.read();
4647             if (ch < 0) {
4648                 throw new EOFException();
4649             }
4650             return (byte) ch;
4651         }
4652 
4653         @Override
readShort()4654         public short readShort() throws IOException {
4655             mPosition += 2;
4656             if (mPosition > mLength) {
4657                 throw new EOFException();
4658             }
4659             int ch1 = mDataInputStream.read();
4660             int ch2 = mDataInputStream.read();
4661             if ((ch1 | ch2) < 0) {
4662                 throw new EOFException();
4663             }
4664             if (mByteOrder == LITTLE_ENDIAN) {
4665                 return (short) ((ch2 << 8) + (ch1));
4666             } else if (mByteOrder == BIG_ENDIAN) {
4667                 return (short) ((ch1 << 8) + (ch2));
4668             }
4669             throw new IOException("Invalid byte order: " + mByteOrder);
4670         }
4671 
4672         @Override
readInt()4673         public int readInt() throws IOException {
4674             mPosition += 4;
4675             if (mPosition > mLength) {
4676                 throw new EOFException();
4677             }
4678             int ch1 = mDataInputStream.read();
4679             int ch2 = mDataInputStream.read();
4680             int ch3 = mDataInputStream.read();
4681             int ch4 = mDataInputStream.read();
4682             if ((ch1 | ch2 | ch3 | ch4) < 0) {
4683                 throw new EOFException();
4684             }
4685             if (mByteOrder == LITTLE_ENDIAN) {
4686                 return ((ch4 << 24) + (ch3 << 16) + (ch2 << 8) + ch1);
4687             } else if (mByteOrder == BIG_ENDIAN) {
4688                 return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4);
4689             }
4690             throw new IOException("Invalid byte order: " + mByteOrder);
4691         }
4692 
4693         @Override
skipBytes(int byteCount)4694         public int skipBytes(int byteCount) throws IOException {
4695             int totalSkip = Math.min(byteCount, mLength - mPosition);
4696             int skipped = 0;
4697             while (skipped < totalSkip) {
4698                 skipped += mDataInputStream.skipBytes(totalSkip - skipped);
4699             }
4700             mPosition += skipped;
4701             return skipped;
4702         }
4703 
readUnsignedShort()4704         public int readUnsignedShort() throws IOException {
4705             mPosition += 2;
4706             if (mPosition > mLength) {
4707                 throw new EOFException();
4708             }
4709             int ch1 = mDataInputStream.read();
4710             int ch2 = mDataInputStream.read();
4711             if ((ch1 | ch2) < 0) {
4712                 throw new EOFException();
4713             }
4714             if (mByteOrder == LITTLE_ENDIAN) {
4715                 return ((ch2 << 8) + (ch1));
4716             } else if (mByteOrder == BIG_ENDIAN) {
4717                 return ((ch1 << 8) + (ch2));
4718             }
4719             throw new IOException("Invalid byte order: " + mByteOrder);
4720         }
4721 
readUnsignedInt()4722         public long readUnsignedInt() throws IOException {
4723             return readInt() & 0xffffffffL;
4724         }
4725 
4726         @Override
readLong()4727         public long readLong() throws IOException {
4728             mPosition += 8;
4729             if (mPosition > mLength) {
4730                 throw new EOFException();
4731             }
4732             int ch1 = mDataInputStream.read();
4733             int ch2 = mDataInputStream.read();
4734             int ch3 = mDataInputStream.read();
4735             int ch4 = mDataInputStream.read();
4736             int ch5 = mDataInputStream.read();
4737             int ch6 = mDataInputStream.read();
4738             int ch7 = mDataInputStream.read();
4739             int ch8 = mDataInputStream.read();
4740             if ((ch1 | ch2 | ch3 | ch4 | ch5 | ch6 | ch7 | ch8) < 0) {
4741                 throw new EOFException();
4742             }
4743             if (mByteOrder == LITTLE_ENDIAN) {
4744                 return (((long) ch8 << 56) + ((long) ch7 << 48) + ((long) ch6 << 40)
4745                         + ((long) ch5 << 32) + ((long) ch4 << 24) + ((long) ch3 << 16)
4746                         + ((long) ch2 << 8) + (long) ch1);
4747             } else if (mByteOrder == BIG_ENDIAN) {
4748                 return (((long) ch1 << 56) + ((long) ch2 << 48) + ((long) ch3 << 40)
4749                         + ((long) ch4 << 32) + ((long) ch5 << 24) + ((long) ch6 << 16)
4750                         + ((long) ch7 << 8) + (long) ch8);
4751             }
4752             throw new IOException("Invalid byte order: " + mByteOrder);
4753         }
4754 
4755         @Override
readFloat()4756         public float readFloat() throws IOException {
4757             return Float.intBitsToFloat(readInt());
4758         }
4759 
4760         @Override
readDouble()4761         public double readDouble() throws IOException {
4762             return Double.longBitsToDouble(readLong());
4763         }
4764 
getLength()4765         public int getLength() {
4766             return mLength;
4767         }
4768     }
4769 
4770     // An output stream to write EXIF data area, which can be written in either little or big endian
4771     // order.
4772     private static class ByteOrderedDataOutputStream extends FilterOutputStream {
4773         final OutputStream mOutputStream;
4774         private ByteOrder mByteOrder;
4775 
ByteOrderedDataOutputStream(OutputStream out, ByteOrder byteOrder)4776         public ByteOrderedDataOutputStream(OutputStream out, ByteOrder byteOrder) {
4777             super(out);
4778             mOutputStream = out;
4779             mByteOrder = byteOrder;
4780         }
4781 
setByteOrder(ByteOrder byteOrder)4782         public void setByteOrder(ByteOrder byteOrder) {
4783             mByteOrder = byteOrder;
4784         }
4785 
write(byte[] bytes)4786         public void write(byte[] bytes) throws IOException {
4787             mOutputStream.write(bytes);
4788         }
4789 
write(byte[] bytes, int offset, int length)4790         public void write(byte[] bytes, int offset, int length) throws IOException {
4791             mOutputStream.write(bytes, offset, length);
4792         }
4793 
writeByte(int val)4794         public void writeByte(int val) throws IOException {
4795             mOutputStream.write(val);
4796         }
4797 
writeShort(short val)4798         public void writeShort(short val) throws IOException {
4799             if (mByteOrder == ByteOrder.LITTLE_ENDIAN) {
4800                 mOutputStream.write((val >>> 0) & 0xFF);
4801                 mOutputStream.write((val >>> 8) & 0xFF);
4802             } else if (mByteOrder == ByteOrder.BIG_ENDIAN) {
4803                 mOutputStream.write((val >>> 8) & 0xFF);
4804                 mOutputStream.write((val >>> 0) & 0xFF);
4805             }
4806         }
4807 
writeInt(int val)4808         public void writeInt(int val) throws IOException {
4809             if (mByteOrder == ByteOrder.LITTLE_ENDIAN) {
4810                 mOutputStream.write((val >>> 0) & 0xFF);
4811                 mOutputStream.write((val >>> 8) & 0xFF);
4812                 mOutputStream.write((val >>> 16) & 0xFF);
4813                 mOutputStream.write((val >>> 24) & 0xFF);
4814             } else if (mByteOrder == ByteOrder.BIG_ENDIAN) {
4815                 mOutputStream.write((val >>> 24) & 0xFF);
4816                 mOutputStream.write((val >>> 16) & 0xFF);
4817                 mOutputStream.write((val >>> 8) & 0xFF);
4818                 mOutputStream.write((val >>> 0) & 0xFF);
4819             }
4820         }
4821 
writeUnsignedShort(int val)4822         public void writeUnsignedShort(int val) throws IOException {
4823             writeShort((short) val);
4824         }
4825 
writeUnsignedInt(long val)4826         public void writeUnsignedInt(long val) throws IOException {
4827             writeInt((int) val);
4828         }
4829     }
4830 
4831     // Swaps image data based on image size
swapBasedOnImageSize(@fdType int firstIfdType, @IfdType int secondIfdType)4832     private void swapBasedOnImageSize(@IfdType int firstIfdType, @IfdType int secondIfdType)
4833             throws IOException {
4834         if (mAttributes[firstIfdType].isEmpty() || mAttributes[secondIfdType].isEmpty()) {
4835             if (DEBUG) {
4836                 Log.d(TAG, "Cannot perform swap since only one image data exists");
4837             }
4838             return;
4839         }
4840 
4841         ExifAttribute firstImageLengthAttribute =
4842                 (ExifAttribute) mAttributes[firstIfdType].get(TAG_IMAGE_LENGTH);
4843         ExifAttribute firstImageWidthAttribute =
4844                 (ExifAttribute) mAttributes[firstIfdType].get(TAG_IMAGE_WIDTH);
4845         ExifAttribute secondImageLengthAttribute =
4846                 (ExifAttribute) mAttributes[secondIfdType].get(TAG_IMAGE_LENGTH);
4847         ExifAttribute secondImageWidthAttribute =
4848                 (ExifAttribute) mAttributes[secondIfdType].get(TAG_IMAGE_WIDTH);
4849 
4850         if (firstImageLengthAttribute == null || firstImageWidthAttribute == null) {
4851             if (DEBUG) {
4852                 Log.d(TAG, "First image does not contain valid size information");
4853             }
4854         } else if (secondImageLengthAttribute == null || secondImageWidthAttribute == null) {
4855             if (DEBUG) {
4856                 Log.d(TAG, "Second image does not contain valid size information");
4857             }
4858         } else {
4859             int firstImageLengthValue = firstImageLengthAttribute.getIntValue(mExifByteOrder);
4860             int firstImageWidthValue = firstImageWidthAttribute.getIntValue(mExifByteOrder);
4861             int secondImageLengthValue = secondImageLengthAttribute.getIntValue(mExifByteOrder);
4862             int secondImageWidthValue = secondImageWidthAttribute.getIntValue(mExifByteOrder);
4863 
4864             if (firstImageLengthValue < secondImageLengthValue &&
4865                     firstImageWidthValue < secondImageWidthValue) {
4866                 HashMap tempMap = mAttributes[firstIfdType];
4867                 mAttributes[firstIfdType] = mAttributes[secondIfdType];
4868                 mAttributes[secondIfdType] = tempMap;
4869             }
4870         }
4871     }
4872 
4873     // Checks if there is a match
containsMatch(byte[] mainBytes, byte[] findBytes)4874     private boolean containsMatch(byte[] mainBytes, byte[] findBytes) {
4875         for (int i = 0; i < mainBytes.length - findBytes.length; i++) {
4876             for (int j = 0; j < findBytes.length; j++) {
4877                 if (mainBytes[i + j] != findBytes[j]) {
4878                     break;
4879                 }
4880                 if (j == findBytes.length - 1) {
4881                     return true;
4882                 }
4883             }
4884         }
4885         return false;
4886     }
4887 
4888     /**
4889      * Copies the given number of the bytes from {@code in} to {@code out}. Neither stream is
4890      * closed.
4891      */
copy(InputStream in, OutputStream out, int numBytes)4892     private static void copy(InputStream in, OutputStream out, int numBytes) throws IOException {
4893         int remainder = numBytes;
4894         byte[] buffer = new byte[8192];
4895         while (remainder > 0) {
4896             int bytesToRead = Math.min(remainder, 8192);
4897             int bytesRead = in.read(buffer, 0, bytesToRead);
4898             if (bytesRead != bytesToRead) {
4899                 throw new IOException("Failed to copy the given amount of bytes from the input"
4900                         + "stream to the output stream.");
4901             }
4902             remainder -= bytesRead;
4903             out.write(buffer, 0, bytesRead);
4904         }
4905     }
4906 
4907     /**
4908      * Convert given int[] to long[]. If long[] is given, just return it.
4909      * Return null for other types of input.
4910      */
convertToLongArray(Object inputObj)4911     private static long[] convertToLongArray(Object inputObj) {
4912         if (inputObj instanceof int[]) {
4913             int[] input = (int[]) inputObj;
4914             long[] result = new long[input.length];
4915             for (int i = 0; i < input.length; i++) {
4916                 result[i] = input[i];
4917             }
4918             return result;
4919         } else if (inputObj instanceof long[]) {
4920             return (long[]) inputObj;
4921         }
4922         return null;
4923     }
4924 
byteArrayToHexString(byte[] bytes)4925     private static String byteArrayToHexString(byte[] bytes) {
4926         StringBuilder sb = new StringBuilder(bytes.length * 2);
4927         for (int i = 0; i < bytes.length; i++) {
4928             sb.append(String.format("%02x", bytes[i]));
4929         }
4930         return sb.toString();
4931     }
4932 }
4933