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