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