• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.media;
18 
19 import static android.media.ExifInterfaceUtils.byteArrayToHexString;
20 import static android.media.ExifInterfaceUtils.closeFileDescriptor;
21 import static android.media.ExifInterfaceUtils.closeQuietly;
22 import static android.media.ExifInterfaceUtils.convertToLongArray;
23 import static android.media.ExifInterfaceUtils.copy;
24 import static android.media.ExifInterfaceUtils.startsWith;
25 
26 import android.annotation.CurrentTimeMillisLong;
27 import android.annotation.IntDef;
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.compat.annotation.UnsupportedAppUsage;
31 import android.content.res.AssetManager;
32 import android.graphics.Bitmap;
33 import android.graphics.BitmapFactory;
34 import android.os.FileUtils;
35 import android.os.ParcelFileDescriptor;
36 import android.system.ErrnoException;
37 import android.system.Os;
38 import android.system.OsConstants;
39 import android.util.Log;
40 import android.util.Pair;
41 
42 import com.android.internal.annotations.GuardedBy;
43 
44 import java.io.BufferedInputStream;
45 import java.io.BufferedOutputStream;
46 import java.io.ByteArrayInputStream;
47 import java.io.ByteArrayOutputStream;
48 import java.io.DataInput;
49 import java.io.DataInputStream;
50 import java.io.EOFException;
51 import java.io.File;
52 import java.io.FileDescriptor;
53 import java.io.FileInputStream;
54 import java.io.FileNotFoundException;
55 import java.io.FileOutputStream;
56 import java.io.FilterOutputStream;
57 import java.io.IOException;
58 import java.io.InputStream;
59 import java.io.OutputStream;
60 import java.lang.annotation.Retention;
61 import java.lang.annotation.RetentionPolicy;
62 import java.nio.ByteBuffer;
63 import java.nio.ByteOrder;
64 import java.nio.charset.Charset;
65 import java.text.ParsePosition;
66 import java.text.SimpleDateFormat;
67 import java.util.Arrays;
68 import java.util.Date;
69 import java.util.HashMap;
70 import java.util.HashSet;
71 import java.util.Locale;
72 import java.util.Map;
73 import java.util.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.d(
2041                     TAG,
2042                     "Invalid image: ExifInterface got an unsupported or corrupted image file",
2043                     e);
2044         } finally {
2045             addDefaultValuesForCompatibility();
2046 
2047             if (DEBUG) {
2048                 printAttributes();
2049             }
2050         }
2051     }
2052 
isSeekableFD(FileDescriptor fd)2053     private static boolean isSeekableFD(FileDescriptor fd) {
2054         try {
2055             Os.lseek(fd, 0, OsConstants.SEEK_CUR);
2056             return true;
2057         } catch (ErrnoException e) {
2058             if (DEBUG) {
2059                 Log.d(TAG, "The file descriptor for the given input is not seekable");
2060             }
2061             return false;
2062         }
2063     }
2064 
2065     // Prints out attributes for debugging.
printAttributes()2066     private void printAttributes() {
2067         for (int i = 0; i < mAttributes.length; ++i) {
2068             Log.d(TAG, "The size of tag group[" + i + "]: " + mAttributes[i].size());
2069             for (Map.Entry entry : (Set<Map.Entry>) mAttributes[i].entrySet()) {
2070                 final ExifAttribute tagValue = (ExifAttribute) entry.getValue();
2071                 Log.d(TAG, "tagName: " + entry.getKey() + ", tagType: " + tagValue.toString()
2072                         + ", tagValue: '" + tagValue.getStringValue(mExifByteOrder) + "'");
2073             }
2074         }
2075     }
2076 
2077     /**
2078      * Save the tag data into the original image file. This is expensive because
2079      * it involves copying all the data from one file to another and deleting
2080      * the old file and renaming the other. It's best to use
2081      * {@link #setAttribute(String,String)} to set all attributes to write and
2082      * make a single call rather than multiple calls for each attribute.
2083      * <p>
2084      * This method is supported for JPEG, PNG, and WebP files.
2085      * <p class="note">
2086      * Note: after calling this method, any attempts to obtain range information
2087      * from {@link #getAttributeRange(String)} or {@link #getThumbnailRange()}
2088      * will throw {@link IllegalStateException}, since the offsets may have
2089      * changed in the newly written file.
2090      * <p>
2091      * For WebP format, the Exif data will be stored as an Extended File Format, and it may not be
2092      * supported for older readers.
2093      * <p>
2094      * For PNG format, the Exif data will be stored as an "eXIf" chunk as per
2095      * "Extensions to the PNG 1.2 Specification, Version 1.5.0".
2096      * <p>
2097      * <b>Warning:</b> Calling this method on a DNG-based instance of {@code ExifInterface} may
2098      * result in the original image file being overwritten with invalid data on some versions of
2099      * Android 13 (API 33).
2100      */
saveAttributes()2101     public void saveAttributes() throws IOException {
2102         if (!isSupportedFormatForSavingAttributes()) {
2103             throw new IOException("ExifInterface only supports saving attributes for JPEG, PNG, "
2104                     + "and WebP formats.");
2105         }
2106         if (mIsInputStream || (mSeekableFileDescriptor == null && mFilename == null)) {
2107             throw new IOException(
2108                     "ExifInterface does not support saving attributes for the current input.");
2109         }
2110         if (mHasThumbnail && mHasThumbnailStrips && !mAreThumbnailStripsConsecutive) {
2111             throw new IOException("ExifInterface does not support saving attributes when the image "
2112                     + "file has non-consecutive thumbnail strips");
2113         }
2114 
2115         // Remember the fact that we've changed the file on disk from what was
2116         // originally parsed, meaning we can't answer range questions
2117         mModified = true;
2118 
2119         // Keep the thumbnail in memory
2120         mThumbnailBytes = getThumbnail();
2121 
2122         FileInputStream in = null;
2123         FileOutputStream out = null;
2124         File tempFile = null;
2125         try {
2126             // Copy the original file to temporary file.
2127             tempFile = File.createTempFile("temp", "tmp");
2128             if (mFilename != null) {
2129                 in = new FileInputStream(mFilename);
2130             } else if (mSeekableFileDescriptor != null) {
2131                 Os.lseek(mSeekableFileDescriptor, 0, OsConstants.SEEK_SET);
2132                 in = new FileInputStream(mSeekableFileDescriptor);
2133             }
2134             out = new FileOutputStream(tempFile);
2135             copy(in, out);
2136         } catch (Exception e) {
2137             throw new IOException("Failed to copy original file to temp file", e);
2138         } finally {
2139             closeQuietly(in);
2140             closeQuietly(out);
2141         }
2142 
2143         in = null;
2144         out = null;
2145         try {
2146             // Save the new file.
2147             in = new FileInputStream(tempFile);
2148             if (mFilename != null) {
2149                 out = new FileOutputStream(mFilename);
2150             } else if (mSeekableFileDescriptor != null) {
2151                 Os.lseek(mSeekableFileDescriptor, 0, OsConstants.SEEK_SET);
2152                 out = new FileOutputStream(mSeekableFileDescriptor);
2153             }
2154             try (BufferedInputStream bufferedIn = new BufferedInputStream(in);
2155                  BufferedOutputStream bufferedOut = new BufferedOutputStream(out)) {
2156                 if (mMimeType == IMAGE_TYPE_JPEG) {
2157                     saveJpegAttributes(bufferedIn, bufferedOut);
2158                 } else if (mMimeType == IMAGE_TYPE_PNG) {
2159                     savePngAttributes(bufferedIn, bufferedOut);
2160                 } else if (mMimeType == IMAGE_TYPE_WEBP) {
2161                     saveWebpAttributes(bufferedIn, bufferedOut);
2162                 }
2163             }
2164         } catch (Exception e) {
2165             // Restore original file
2166             in = new FileInputStream(tempFile);
2167             if (mFilename != null) {
2168                 out = new FileOutputStream(mFilename);
2169             } else if (mSeekableFileDescriptor != null) {
2170                 try {
2171                     Os.lseek(mSeekableFileDescriptor, 0, OsConstants.SEEK_SET);
2172                 } catch (ErrnoException exception) {
2173                     throw new IOException("Failed to save new file. Original file may be "
2174                             + "corrupted since error occurred while trying to restore it.",
2175                             exception);
2176                 }
2177                 out = new FileOutputStream(mSeekableFileDescriptor);
2178             }
2179             copy(in, out);
2180             closeQuietly(in);
2181             closeQuietly(out);
2182             throw new IOException("Failed to save new file", e);
2183         } finally {
2184             closeQuietly(in);
2185             closeQuietly(out);
2186             tempFile.delete();
2187         }
2188 
2189         // Discard the thumbnail in memory
2190         mThumbnailBytes = null;
2191     }
2192 
2193     /**
2194      * Returns true if the image file has a thumbnail.
2195      */
hasThumbnail()2196     public boolean hasThumbnail() {
2197         return mHasThumbnail;
2198     }
2199 
2200     /**
2201      * Returns true if the image file has the given attribute defined.
2202      *
2203      * @param tag the name of the tag.
2204      */
hasAttribute(@onNull String tag)2205     public boolean hasAttribute(@NonNull String tag) {
2206         return (getExifAttribute(tag) != null);
2207     }
2208 
2209     /**
2210      * Returns the JPEG compressed thumbnail inside the image file, or {@code null} if there is no
2211      * JPEG compressed thumbnail.
2212      * The returned data can be decoded using
2213      * {@link android.graphics.BitmapFactory#decodeByteArray(byte[],int,int)}
2214      */
getThumbnail()2215     public byte[] getThumbnail() {
2216         if (mThumbnailCompression == DATA_JPEG || mThumbnailCompression == DATA_JPEG_COMPRESSED) {
2217             return getThumbnailBytes();
2218         }
2219         return null;
2220     }
2221 
2222     /**
2223      * Returns the thumbnail bytes inside the image file, regardless of the compression type of the
2224      * thumbnail image.
2225      */
getThumbnailBytes()2226     public byte[] getThumbnailBytes() {
2227         if (!mHasThumbnail) {
2228             return null;
2229         }
2230         if (mThumbnailBytes != null) {
2231             return mThumbnailBytes;
2232         }
2233 
2234         // Read the thumbnail.
2235         InputStream in = null;
2236         FileDescriptor newFileDescriptor = null;
2237         try {
2238             if (mAssetInputStream != null) {
2239                 in = mAssetInputStream;
2240                 if (in.markSupported()) {
2241                     in.reset();
2242                 } else {
2243                     Log.d(TAG, "Cannot read thumbnail from inputstream without mark/reset support");
2244                     return null;
2245                 }
2246             } else if (mFilename != null) {
2247                 in = new FileInputStream(mFilename);
2248             } else if (mSeekableFileDescriptor != null) {
2249                 newFileDescriptor = Os.dup(mSeekableFileDescriptor);
2250                 Os.lseek(newFileDescriptor, 0, OsConstants.SEEK_SET);
2251                 in = new FileInputStream(newFileDescriptor);
2252             }
2253             if (in == null) {
2254                 // Should not be reached this.
2255                 throw new FileNotFoundException();
2256             }
2257             if (in.skip(mThumbnailOffset) != mThumbnailOffset) {
2258                 throw new IOException("Corrupted image");
2259             }
2260             // TODO: Need to handle potential OutOfMemoryError
2261             byte[] buffer = new byte[mThumbnailLength];
2262             if (in.read(buffer) != mThumbnailLength) {
2263                 throw new IOException("Corrupted image");
2264             }
2265             mThumbnailBytes = buffer;
2266             return buffer;
2267         } catch (IOException | ErrnoException e) {
2268             // Couldn't get a thumbnail image.
2269             Log.d(TAG, "Encountered exception while getting thumbnail", e);
2270         } finally {
2271             closeQuietly(in);
2272             if (newFileDescriptor != null) {
2273                 closeFileDescriptor(newFileDescriptor);
2274             }
2275         }
2276         return null;
2277     }
2278 
2279     /**
2280      * Creates and returns a Bitmap object of the thumbnail image based on the byte array and the
2281      * thumbnail compression value, or {@code null} if the compression type is unsupported.
2282      */
getThumbnailBitmap()2283     public Bitmap getThumbnailBitmap() {
2284         if (!mHasThumbnail) {
2285             return null;
2286         } else if (mThumbnailBytes == null) {
2287             mThumbnailBytes = getThumbnailBytes();
2288         }
2289 
2290         if (mThumbnailCompression == DATA_JPEG || mThumbnailCompression == DATA_JPEG_COMPRESSED) {
2291             return BitmapFactory.decodeByteArray(mThumbnailBytes, 0, mThumbnailLength);
2292         } else if (mThumbnailCompression == DATA_UNCOMPRESSED) {
2293             int[] rgbValues = new int[mThumbnailBytes.length / 3];
2294             byte alpha = (byte) 0xff000000;
2295             for (int i = 0; i < rgbValues.length; i++) {
2296                 rgbValues[i] = alpha + (mThumbnailBytes[3 * i] << 16)
2297                         + (mThumbnailBytes[3 * i + 1] << 8) + mThumbnailBytes[3 * i + 2];
2298             }
2299 
2300             ExifAttribute imageLengthAttribute =
2301                     (ExifAttribute) mAttributes[IFD_TYPE_THUMBNAIL].get(TAG_THUMBNAIL_IMAGE_LENGTH);
2302             ExifAttribute imageWidthAttribute =
2303                     (ExifAttribute) mAttributes[IFD_TYPE_THUMBNAIL].get(TAG_THUMBNAIL_IMAGE_WIDTH);
2304             if (imageLengthAttribute != null && imageWidthAttribute != null) {
2305                 int imageLength = imageLengthAttribute.getIntValue(mExifByteOrder);
2306                 int imageWidth = imageWidthAttribute.getIntValue(mExifByteOrder);
2307                 return Bitmap.createBitmap(
2308                         rgbValues, imageWidth, imageLength, Bitmap.Config.ARGB_8888);
2309             }
2310         }
2311         return null;
2312     }
2313 
2314     /**
2315      * Returns true if thumbnail image is JPEG Compressed, or false if either thumbnail image does
2316      * not exist or thumbnail image is uncompressed.
2317      */
isThumbnailCompressed()2318     public boolean isThumbnailCompressed() {
2319         if (!mHasThumbnail) {
2320             return false;
2321         }
2322         if (mThumbnailCompression == DATA_JPEG || mThumbnailCompression == DATA_JPEG_COMPRESSED) {
2323             return true;
2324         }
2325         return false;
2326     }
2327 
2328     /**
2329      * Returns the offset and length of thumbnail inside the image file, or
2330      * {@code null} if either there is no thumbnail or the thumbnail bytes are stored
2331      * non-consecutively.
2332      *
2333      * @return two-element array, the offset in the first value, and length in
2334      *         the second, or {@code null} if no thumbnail was found or the thumbnail strips are
2335      *         not placed consecutively.
2336      * @throws IllegalStateException if {@link #saveAttributes()} has been
2337      *             called since the underlying file was initially parsed, since
2338      *             that means offsets may have changed.
2339      */
getThumbnailRange()2340     public @Nullable long[] getThumbnailRange() {
2341         if (mModified) {
2342             throw new IllegalStateException(
2343                     "The underlying file has been modified since being parsed");
2344         }
2345 
2346         if (mHasThumbnail) {
2347             if (mHasThumbnailStrips && !mAreThumbnailStripsConsecutive) {
2348                 return null;
2349             }
2350             return new long[] { mThumbnailOffset, mThumbnailLength };
2351         }
2352         return null;
2353     }
2354 
2355     /**
2356      * Returns the offset and length of the requested tag inside the image file,
2357      * or {@code null} if the tag is not contained.
2358      *
2359      * @return two-element array, the offset in the first value, and length in
2360      *         the second, or {@code null} if no tag was found.
2361      * @throws IllegalStateException if {@link #saveAttributes()} has been
2362      *             called since the underlying file was initially parsed, since
2363      *             that means offsets may have changed.
2364      */
getAttributeRange(@onNull String tag)2365     public @Nullable long[] getAttributeRange(@NonNull String tag) {
2366         if (tag == null) {
2367             throw new NullPointerException("tag shouldn't be null");
2368         }
2369         if (mModified) {
2370             throw new IllegalStateException(
2371                     "The underlying file has been modified since being parsed");
2372         }
2373 
2374         final ExifAttribute attribute = getExifAttribute(tag);
2375         if (attribute != null) {
2376             return new long[] { attribute.bytesOffset, attribute.bytes.length };
2377         } else {
2378             return null;
2379         }
2380     }
2381 
2382     /**
2383      * Returns the raw bytes for the value of the requested tag inside the image
2384      * file, or {@code null} if the tag is not contained.
2385      *
2386      * @return raw bytes for the value of the requested tag, or {@code null} if
2387      *         no tag was found.
2388      */
getAttributeBytes(@onNull String tag)2389     public @Nullable byte[] getAttributeBytes(@NonNull String tag) {
2390         if (tag == null) {
2391             throw new NullPointerException("tag shouldn't be null");
2392         }
2393         final ExifAttribute attribute = getExifAttribute(tag);
2394         if (attribute != null) {
2395             return attribute.bytes;
2396         } else {
2397             return null;
2398         }
2399     }
2400 
2401     /**
2402      * Stores the latitude and longitude value in a float array. The first element is
2403      * the latitude, and the second element is the longitude. Returns false if the
2404      * Exif tags are not available.
2405      */
getLatLong(float output[])2406     public boolean getLatLong(float output[]) {
2407         String latValue = getAttribute(TAG_GPS_LATITUDE);
2408         String latRef = getAttribute(TAG_GPS_LATITUDE_REF);
2409         String lngValue = getAttribute(TAG_GPS_LONGITUDE);
2410         String lngRef = getAttribute(TAG_GPS_LONGITUDE_REF);
2411 
2412         if (latValue != null && latRef != null && lngValue != null && lngRef != null) {
2413             try {
2414                 output[0] = convertRationalLatLonToFloat(latValue, latRef);
2415                 output[1] = convertRationalLatLonToFloat(lngValue, lngRef);
2416                 return true;
2417             } catch (IllegalArgumentException e) {
2418                 // if values are not parseable
2419             }
2420         }
2421 
2422         return false;
2423     }
2424 
2425     /**
2426      * Return the altitude in meters. If the exif tag does not exist, return
2427      * <var>defaultValue</var>.
2428      *
2429      * @param defaultValue the value to return if the tag is not available.
2430      */
getAltitude(double defaultValue)2431     public double getAltitude(double defaultValue) {
2432         double altitude = getAttributeDouble(TAG_GPS_ALTITUDE, -1);
2433         int ref = getAttributeInt(TAG_GPS_ALTITUDE_REF, -1);
2434 
2435         if (altitude >= 0 && ref >= 0) {
2436             return (altitude * ((ref == 1) ? -1 : 1));
2437         } else {
2438             return defaultValue;
2439         }
2440     }
2441 
2442     /**
2443      * Returns parsed {@link #TAG_DATETIME} value, or -1 if unavailable or invalid.
2444      */
getDateTime()2445     public @CurrentTimeMillisLong long getDateTime() {
2446         return parseDateTime(getAttribute(TAG_DATETIME),
2447                 getAttribute(TAG_SUBSEC_TIME),
2448                 getAttribute(TAG_OFFSET_TIME));
2449     }
2450 
2451     /**
2452      * Returns parsed {@link #TAG_DATETIME_DIGITIZED} value, or -1 if unavailable or invalid.
2453      */
getDateTimeDigitized()2454     public @CurrentTimeMillisLong long getDateTimeDigitized() {
2455         return parseDateTime(getAttribute(TAG_DATETIME_DIGITIZED),
2456                 getAttribute(TAG_SUBSEC_TIME_DIGITIZED),
2457                 getAttribute(TAG_OFFSET_TIME_DIGITIZED));
2458     }
2459 
2460     /**
2461      * Returns parsed {@link #TAG_DATETIME_ORIGINAL} value, or -1 if unavailable or invalid.
2462      */
getDateTimeOriginal()2463     public @CurrentTimeMillisLong long getDateTimeOriginal() {
2464         return parseDateTime(getAttribute(TAG_DATETIME_ORIGINAL),
2465                 getAttribute(TAG_SUBSEC_TIME_ORIGINAL),
2466                 getAttribute(TAG_OFFSET_TIME_ORIGINAL));
2467     }
2468 
parseDateTime(@ullable String dateTimeString, @Nullable String subSecs, @Nullable String offsetString)2469     private static @CurrentTimeMillisLong long parseDateTime(@Nullable String dateTimeString,
2470             @Nullable String subSecs, @Nullable String offsetString) {
2471         if (dateTimeString == null
2472                 || !sNonZeroTimePattern.matcher(dateTimeString).matches()) return -1;
2473 
2474         ParsePosition pos = new ParsePosition(0);
2475         try {
2476             // The exif field is in local time. Parsing it as if it is UTC will yield time
2477             // since 1/1/1970 local time
2478             Date datetime;
2479             synchronized (sFormatter) {
2480                 datetime = sFormatter.parse(dateTimeString, pos);
2481             }
2482 
2483             if (offsetString != null) {
2484                 dateTimeString = dateTimeString + " " + offsetString;
2485                 ParsePosition position = new ParsePosition(0);
2486                 synchronized (sFormatterTz) {
2487                     datetime = sFormatterTz.parse(dateTimeString, position);
2488                 }
2489             }
2490 
2491             if (datetime == null) return -1;
2492             long msecs = datetime.getTime();
2493 
2494             if (subSecs != null) {
2495                 try {
2496                     long sub = Long.parseLong(subSecs);
2497                     while (sub > 1000) {
2498                         sub /= 10;
2499                     }
2500                     msecs += sub;
2501                 } catch (NumberFormatException e) {
2502                     // Ignored
2503                 }
2504             }
2505             return msecs;
2506         } catch (IllegalArgumentException e) {
2507             return -1;
2508         }
2509     }
2510 
2511     /**
2512      * Returns number of milliseconds since Jan. 1, 1970, midnight UTC.
2513      * Returns -1 if the date time information if not available.
2514      */
getGpsDateTime()2515     public long getGpsDateTime() {
2516         String date = getAttribute(TAG_GPS_DATESTAMP);
2517         String time = getAttribute(TAG_GPS_TIMESTAMP);
2518         if (date == null || time == null
2519                 || (!sNonZeroTimePattern.matcher(date).matches()
2520                 && !sNonZeroTimePattern.matcher(time).matches())) {
2521             return -1;
2522         }
2523 
2524         String dateTimeString = date + ' ' + time;
2525 
2526         ParsePosition pos = new ParsePosition(0);
2527         try {
2528             final Date datetime;
2529             synchronized (sFormatter) {
2530                 datetime = sFormatter.parse(dateTimeString, pos);
2531             }
2532             if (datetime == null) return -1;
2533             return datetime.getTime();
2534         } catch (IllegalArgumentException | ArrayIndexOutOfBoundsException e) {
2535             return -1;
2536         }
2537     }
2538 
2539     /** {@hide} */
convertRationalLatLonToFloat(String rationalString, String ref)2540     public static float convertRationalLatLonToFloat(String rationalString, String ref) {
2541         try {
2542             String [] parts = rationalString.split(",");
2543 
2544             String [] pair;
2545             pair = parts[0].split("/");
2546             double degrees = Double.parseDouble(pair[0].trim())
2547                     / Double.parseDouble(pair[1].trim());
2548 
2549             pair = parts[1].split("/");
2550             double minutes = Double.parseDouble(pair[0].trim())
2551                     / Double.parseDouble(pair[1].trim());
2552 
2553             pair = parts[2].split("/");
2554             double seconds = Double.parseDouble(pair[0].trim())
2555                     / Double.parseDouble(pair[1].trim());
2556 
2557             double result = degrees + (minutes / 60.0) + (seconds / 3600.0);
2558             if ((ref.equals("S") || ref.equals("W"))) {
2559                 return (float) -result;
2560             }
2561             return (float) result;
2562         } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
2563             // Not valid
2564             throw new IllegalArgumentException();
2565         }
2566     }
2567 
initForFilename(String filename)2568     private void initForFilename(String filename) throws IOException {
2569         FileInputStream in = null;
2570         ParcelFileDescriptor modernFd = null;
2571         mAssetInputStream = null;
2572         mFilename = filename;
2573         mIsInputStream = false;
2574         try {
2575             in = new FileInputStream(filename);
2576             modernFd = FileUtils.convertToModernFd(in.getFD());
2577             if (modernFd != null) {
2578                 closeQuietly(in);
2579                 in = new FileInputStream(modernFd.getFileDescriptor());
2580                 mSeekableFileDescriptor = null;
2581             } else if (isSeekableFD(in.getFD())) {
2582                 mSeekableFileDescriptor = in.getFD();
2583             }
2584             loadAttributes(in);
2585         } finally {
2586             closeQuietly(in);
2587             if (modernFd != null) {
2588                 modernFd.close();
2589             }
2590         }
2591     }
2592 
2593     // Checks the type of image file
getMimeType(BufferedInputStream in)2594     private int getMimeType(BufferedInputStream in) throws IOException {
2595         // TODO (b/142218289): Need to handle case where input stream does not support mark
2596         in.mark(SIGNATURE_CHECK_SIZE);
2597         byte[] signatureCheckBytes = new byte[SIGNATURE_CHECK_SIZE];
2598         in.read(signatureCheckBytes);
2599         in.reset();
2600         if (isJpegFormat(signatureCheckBytes)) {
2601             return IMAGE_TYPE_JPEG;
2602         } else if (isRafFormat(signatureCheckBytes)) {
2603             return IMAGE_TYPE_RAF;
2604         } else if (isHeifFormat(signatureCheckBytes)) {
2605             return IMAGE_TYPE_HEIF;
2606         } else if (isOrfFormat(signatureCheckBytes)) {
2607             return IMAGE_TYPE_ORF;
2608         } else if (isRw2Format(signatureCheckBytes)) {
2609             return IMAGE_TYPE_RW2;
2610         } else if (isPngFormat(signatureCheckBytes)) {
2611             return IMAGE_TYPE_PNG;
2612         } else if (isWebpFormat(signatureCheckBytes)) {
2613             return IMAGE_TYPE_WEBP;
2614         }
2615         // Certain file formats (PEF) are identified in readImageFileDirectory()
2616         return IMAGE_TYPE_UNKNOWN;
2617     }
2618 
2619     /**
2620      * This method looks at the first 3 bytes to determine if this file is a JPEG file.
2621      * See http://www.media.mit.edu/pia/Research/deepview/exif.html, "JPEG format and Marker"
2622      */
isJpegFormat(byte[] signatureCheckBytes)2623     private static boolean isJpegFormat(byte[] signatureCheckBytes) throws IOException {
2624         for (int i = 0; i < JPEG_SIGNATURE.length; i++) {
2625             if (signatureCheckBytes[i] != JPEG_SIGNATURE[i]) {
2626                 return false;
2627             }
2628         }
2629         return true;
2630     }
2631 
2632     /**
2633      * This method looks at the first 15 bytes to determine if this file is a RAF file.
2634      * There is no official specification for RAF files from Fuji, but there is an online archive of
2635      * image file specifications:
2636      * http://fileformats.archiveteam.org/wiki/Fujifilm_RAF
2637      */
isRafFormat(byte[] signatureCheckBytes)2638     private boolean isRafFormat(byte[] signatureCheckBytes) throws IOException {
2639         byte[] rafSignatureBytes = RAF_SIGNATURE.getBytes();
2640         for (int i = 0; i < rafSignatureBytes.length; i++) {
2641             if (signatureCheckBytes[i] != rafSignatureBytes[i]) {
2642                 return false;
2643             }
2644         }
2645         return true;
2646     }
2647 
isHeifFormat(byte[] signatureCheckBytes)2648     private boolean isHeifFormat(byte[] signatureCheckBytes) throws IOException {
2649         ByteOrderedDataInputStream signatureInputStream = null;
2650         try {
2651             signatureInputStream = new ByteOrderedDataInputStream(signatureCheckBytes);
2652 
2653             long chunkSize = signatureInputStream.readInt();
2654             byte[] chunkType = new byte[4];
2655             signatureInputStream.read(chunkType);
2656 
2657             if (!Arrays.equals(chunkType, HEIF_TYPE_FTYP)) {
2658                 return false;
2659             }
2660 
2661             long chunkDataOffset = 8;
2662             if (chunkSize == 1) {
2663                 // This indicates that the next 8 bytes represent the chunk size,
2664                 // and chunk data comes after that.
2665                 chunkSize = signatureInputStream.readLong();
2666                 if (chunkSize < 16) {
2667                     // The smallest valid chunk is 16 bytes long in this case.
2668                     return false;
2669                 }
2670                 chunkDataOffset += 8;
2671             }
2672 
2673             // only sniff up to signatureCheckBytes.length
2674             if (chunkSize > signatureCheckBytes.length) {
2675                 chunkSize = signatureCheckBytes.length;
2676             }
2677 
2678             long chunkDataSize = chunkSize - chunkDataOffset;
2679 
2680             // It should at least have major brand (4-byte) and minor version (4-byte).
2681             // The rest of the chunk (if any) is a list of (4-byte) compatible brands.
2682             if (chunkDataSize < 8) {
2683                 return false;
2684             }
2685 
2686             byte[] brand = new byte[4];
2687             boolean isMif1 = false;
2688             boolean isHeic = false;
2689             boolean isAvif = false;
2690             for (long i = 0; i < chunkDataSize / 4;  ++i) {
2691                 if (signatureInputStream.read(brand) != brand.length) {
2692                     return false;
2693                 }
2694                 if (i == 1) {
2695                     // Skip this index, it refers to the minorVersion, not a brand.
2696                     continue;
2697                 }
2698                 if (Arrays.equals(brand, HEIF_BRAND_MIF1)) {
2699                     isMif1 = true;
2700                 } else if (Arrays.equals(brand, HEIF_BRAND_HEIC)) {
2701                     isHeic = true;
2702                 } else if (Arrays.equals(brand, HEIF_BRAND_AVIF)
2703                         || Arrays.equals(brand, HEIF_BRAND_AVIS)) {
2704                     isAvif = true;
2705                 }
2706                 if (isMif1 && (isHeic || isAvif)) {
2707                     return true;
2708                 }
2709             }
2710         } catch (Exception e) {
2711             if (DEBUG) {
2712                 Log.d(TAG, "Exception parsing HEIF file type box.", e);
2713             }
2714         } finally {
2715             if (signatureInputStream != null) {
2716                 signatureInputStream.close();
2717                 signatureInputStream = null;
2718             }
2719         }
2720         return false;
2721     }
2722 
2723     /**
2724      * ORF has a similar structure to TIFF but it contains a different signature at the TIFF Header.
2725      * This method looks at the 2 bytes following the Byte Order bytes to determine if this file is
2726      * an ORF file.
2727      * There is no official specification for ORF files from Olympus, but there is an online archive
2728      * of image file specifications:
2729      * http://fileformats.archiveteam.org/wiki/Olympus_ORF
2730      */
isOrfFormat(byte[] signatureCheckBytes)2731     private boolean isOrfFormat(byte[] signatureCheckBytes) throws IOException {
2732         ByteOrderedDataInputStream signatureInputStream = null;
2733 
2734         try {
2735             signatureInputStream = new ByteOrderedDataInputStream(signatureCheckBytes);
2736 
2737             // Read byte order
2738             mExifByteOrder = readByteOrder(signatureInputStream);
2739             // Set byte order
2740             signatureInputStream.setByteOrder(mExifByteOrder);
2741 
2742             short orfSignature = signatureInputStream.readShort();
2743             return orfSignature == ORF_SIGNATURE_1 || orfSignature == ORF_SIGNATURE_2;
2744         } catch (Exception e) {
2745             // Do nothing
2746         } finally {
2747             if (signatureInputStream != null) {
2748                 signatureInputStream.close();
2749             }
2750         }
2751         return false;
2752     }
2753 
2754     /**
2755      * RW2 is TIFF-based, but stores 0x55 signature byte instead of 0x42 at the header
2756      * See http://lclevy.free.fr/raw/
2757      */
isRw2Format(byte[] signatureCheckBytes)2758     private boolean isRw2Format(byte[] signatureCheckBytes) throws IOException {
2759         ByteOrderedDataInputStream signatureInputStream = null;
2760 
2761         try {
2762             signatureInputStream = new ByteOrderedDataInputStream(signatureCheckBytes);
2763 
2764             // Read byte order
2765             mExifByteOrder = readByteOrder(signatureInputStream);
2766             // Set byte order
2767             signatureInputStream.setByteOrder(mExifByteOrder);
2768 
2769             short signatureByte = signatureInputStream.readShort();
2770             signatureInputStream.close();
2771             return signatureByte == RW2_SIGNATURE;
2772         } catch (Exception e) {
2773             // Do nothing
2774         } finally {
2775             if (signatureInputStream != null) {
2776                 signatureInputStream.close();
2777             }
2778         }
2779         return false;
2780     }
2781 
2782     /**
2783      * PNG's file signature is first 8 bytes.
2784      * See PNG (Portable Network Graphics) Specification, Version 1.2, 3.1. PNG file signature
2785      */
isPngFormat(byte[] signatureCheckBytes)2786     private boolean isPngFormat(byte[] signatureCheckBytes) throws IOException {
2787         for (int i = 0; i < PNG_SIGNATURE.length; i++) {
2788             if (signatureCheckBytes[i] != PNG_SIGNATURE[i]) {
2789                 return false;
2790             }
2791         }
2792         return true;
2793     }
2794 
2795     /**
2796      * WebP's file signature is composed of 12 bytes:
2797      *   'RIFF' (4 bytes) + file length value (4 bytes) + 'WEBP' (4 bytes)
2798      * See https://developers.google.com/speed/webp/docs/riff_container, Section "WebP File Header"
2799      */
isWebpFormat(byte[] signatureCheckBytes)2800     private boolean isWebpFormat(byte[] signatureCheckBytes) throws IOException {
2801         for (int i = 0; i < WEBP_SIGNATURE_1.length; i++) {
2802             if (signatureCheckBytes[i] != WEBP_SIGNATURE_1[i]) {
2803                 return false;
2804             }
2805         }
2806         for (int i = 0; i < WEBP_SIGNATURE_2.length; i++) {
2807             if (signatureCheckBytes[i + WEBP_SIGNATURE_1.length + WEBP_FILE_SIZE_BYTE_LENGTH]
2808                     != WEBP_SIGNATURE_2[i]) {
2809                 return false;
2810             }
2811         }
2812         return true;
2813     }
2814 
isExifDataOnly(BufferedInputStream in)2815     private static boolean isExifDataOnly(BufferedInputStream in) throws IOException {
2816         in.mark(IDENTIFIER_EXIF_APP1.length);
2817         byte[] signatureCheckBytes = new byte[IDENTIFIER_EXIF_APP1.length];
2818         in.read(signatureCheckBytes);
2819         in.reset();
2820         for (int i = 0; i < IDENTIFIER_EXIF_APP1.length; i++) {
2821             if (signatureCheckBytes[i] != IDENTIFIER_EXIF_APP1[i]) {
2822                 return false;
2823             }
2824         }
2825         return true;
2826     }
2827 
2828     /**
2829      * Loads EXIF attributes from a JPEG input stream.
2830      *
2831      * @param in The input stream that starts with the JPEG data.
2832      * @param jpegOffset The offset value in input stream for JPEG data.
2833      * @param imageType The image type from which to retrieve metadata. Use IFD_TYPE_PRIMARY for
2834      *                   primary image, IFD_TYPE_PREVIEW for preview image, and
2835      *                   IFD_TYPE_THUMBNAIL for thumbnail image.
2836      * @throws IOException If the data contains invalid JPEG markers, offsets, or length values.
2837      */
getJpegAttributes(ByteOrderedDataInputStream in, int jpegOffset, int imageType)2838     private void getJpegAttributes(ByteOrderedDataInputStream in, int jpegOffset, int imageType)
2839             throws IOException {
2840         // See JPEG File Interchange Format Specification, "JFIF Specification"
2841         if (DEBUG) {
2842             Log.d(TAG, "getJpegAttributes starting with: " + in);
2843         }
2844 
2845         // JPEG uses Big Endian by default. See https://people.cs.umass.edu/~verts/cs32/endian.html
2846         in.setByteOrder(ByteOrder.BIG_ENDIAN);
2847 
2848         // Skip to JPEG data
2849         in.seek(jpegOffset);
2850         int bytesRead = jpegOffset;
2851 
2852         byte marker;
2853         if ((marker = in.readByte()) != MARKER) {
2854             throw new IOException("Invalid marker: " + Integer.toHexString(marker & 0xff));
2855         }
2856         ++bytesRead;
2857         if (in.readByte() != MARKER_SOI) {
2858             throw new IOException("Invalid marker: " + Integer.toHexString(marker & 0xff));
2859         }
2860         ++bytesRead;
2861         while (true) {
2862             marker = in.readByte();
2863             if (marker != MARKER) {
2864                 throw new IOException("Invalid marker:" + Integer.toHexString(marker & 0xff));
2865             }
2866             ++bytesRead;
2867             marker = in.readByte();
2868             if (DEBUG) {
2869                 Log.d(TAG, "Found JPEG segment indicator: " + Integer.toHexString(marker & 0xff));
2870             }
2871             ++bytesRead;
2872 
2873             // EOI indicates the end of an image and in case of SOS, JPEG image stream starts and
2874             // the image data will terminate right after.
2875             if (marker == MARKER_EOI || marker == MARKER_SOS) {
2876                 break;
2877             }
2878             int length = in.readUnsignedShort() - 2;
2879             bytesRead += 2;
2880             if (DEBUG) {
2881                 Log.d(TAG, "JPEG segment: " + Integer.toHexString(marker & 0xff) + " (length: "
2882                         + (length + 2) + ")");
2883             }
2884             if (length < 0) {
2885                 throw new IOException("Invalid length");
2886             }
2887             switch (marker) {
2888                 case MARKER_APP1: {
2889                     final int start = bytesRead;
2890                     final byte[] bytes = new byte[length];
2891                     in.readFully(bytes);
2892                     bytesRead += length;
2893                     length = 0;
2894 
2895                     if (startsWith(bytes, IDENTIFIER_EXIF_APP1)) {
2896                         final long offset = start + IDENTIFIER_EXIF_APP1.length;
2897                         final byte[] value = Arrays.copyOfRange(bytes,
2898                                 IDENTIFIER_EXIF_APP1.length, bytes.length);
2899                         // Save offset values for handleThumbnailFromJfif() function
2900                         mExifOffset = (int) offset;
2901                         readExifSegment(value, imageType);
2902                     } else if (startsWith(bytes, IDENTIFIER_XMP_APP1)) {
2903                         // See XMP Specification Part 3: Storage in Files, 1.1.3 JPEG, Table 6
2904                         final long offset = start + IDENTIFIER_XMP_APP1.length;
2905                         final byte[] value = Arrays.copyOfRange(bytes,
2906                                 IDENTIFIER_XMP_APP1.length, bytes.length);
2907                         // TODO: check if ignoring separate XMP data when tag 700 already exists is
2908                         //  valid.
2909                         if (getAttribute(TAG_XMP) == null) {
2910                             mAttributes[IFD_TYPE_PRIMARY].put(TAG_XMP, new ExifAttribute(
2911                                     IFD_FORMAT_BYTE, value.length, offset, value));
2912                             mXmpIsFromSeparateMarker = true;
2913                         }
2914                     }
2915                     break;
2916                 }
2917 
2918                 case MARKER_COM: {
2919                     byte[] bytes = new byte[length];
2920                     if (in.read(bytes) != length) {
2921                         throw new IOException("Invalid exif");
2922                     }
2923                     length = 0;
2924                     if (getAttribute(TAG_USER_COMMENT) == null) {
2925                         mAttributes[IFD_TYPE_EXIF].put(TAG_USER_COMMENT, ExifAttribute.createString(
2926                                 new String(bytes, ASCII)));
2927                     }
2928                     break;
2929                 }
2930 
2931                 case MARKER_SOF0:
2932                 case MARKER_SOF1:
2933                 case MARKER_SOF2:
2934                 case MARKER_SOF3:
2935                 case MARKER_SOF5:
2936                 case MARKER_SOF6:
2937                 case MARKER_SOF7:
2938                 case MARKER_SOF9:
2939                 case MARKER_SOF10:
2940                 case MARKER_SOF11:
2941                 case MARKER_SOF13:
2942                 case MARKER_SOF14:
2943                 case MARKER_SOF15: {
2944                     if (in.skipBytes(1) != 1) {
2945                         throw new IOException("Invalid SOFx");
2946                     }
2947                     mAttributes[imageType].put(imageType != IFD_TYPE_THUMBNAIL
2948                                     ? TAG_IMAGE_LENGTH : TAG_THUMBNAIL_IMAGE_LENGTH,
2949                             ExifAttribute.createULong(in.readUnsignedShort(), mExifByteOrder));
2950                     mAttributes[imageType].put(imageType != IFD_TYPE_THUMBNAIL
2951                                     ? TAG_IMAGE_WIDTH : TAG_THUMBNAIL_IMAGE_WIDTH,
2952                             ExifAttribute.createULong(in.readUnsignedShort(), mExifByteOrder));
2953                     length -= 5;
2954                     break;
2955                 }
2956 
2957                 default: {
2958                     break;
2959                 }
2960             }
2961             if (length < 0) {
2962                 throw new IOException("Invalid length");
2963             }
2964             if (in.skipBytes(length) != length) {
2965                 throw new IOException("Invalid JPEG segment");
2966             }
2967             bytesRead += length;
2968         }
2969         // Restore original byte order
2970         in.setByteOrder(mExifByteOrder);
2971     }
2972 
getRawAttributes(ByteOrderedDataInputStream in)2973     private void getRawAttributes(ByteOrderedDataInputStream in) throws IOException {
2974         // Parse TIFF Headers. See JEITA CP-3451C Section 4.5.2. Table 1.
2975         parseTiffHeaders(in, in.available());
2976 
2977         // Read TIFF image file directories. See JEITA CP-3451C Section 4.5.2. Figure 6.
2978         readImageFileDirectory(in, IFD_TYPE_PRIMARY);
2979 
2980         // Update ImageLength/Width tags for all image data.
2981         updateImageSizeValues(in, IFD_TYPE_PRIMARY);
2982         updateImageSizeValues(in, IFD_TYPE_PREVIEW);
2983         updateImageSizeValues(in, IFD_TYPE_THUMBNAIL);
2984 
2985         // Check if each image data is in valid position.
2986         validateImages();
2987 
2988         if (mMimeType == IMAGE_TYPE_PEF) {
2989             // PEF files contain a MakerNote data, which contains the data for ColorSpace tag.
2990             // See http://lclevy.free.fr/raw/ and piex.cc PefGetPreviewData()
2991             ExifAttribute makerNoteAttribute =
2992                     (ExifAttribute) mAttributes[IFD_TYPE_EXIF].get(TAG_MAKER_NOTE);
2993             if (makerNoteAttribute != null) {
2994                 // Create an ordered DataInputStream for MakerNote
2995                 ByteOrderedDataInputStream makerNoteDataInputStream =
2996                         new ByteOrderedDataInputStream(makerNoteAttribute.bytes);
2997                 makerNoteDataInputStream.setByteOrder(mExifByteOrder);
2998 
2999                 // Seek to MakerNote data
3000                 makerNoteDataInputStream.seek(PEF_MAKER_NOTE_SKIP_SIZE);
3001 
3002                 // Read IFD data from MakerNote
3003                 readImageFileDirectory(makerNoteDataInputStream, IFD_TYPE_PEF);
3004 
3005                 // Update ColorSpace tag
3006                 ExifAttribute colorSpaceAttribute =
3007                         (ExifAttribute) mAttributes[IFD_TYPE_PEF].get(TAG_COLOR_SPACE);
3008                 if (colorSpaceAttribute != null) {
3009                     mAttributes[IFD_TYPE_EXIF].put(TAG_COLOR_SPACE, colorSpaceAttribute);
3010                 }
3011             }
3012         }
3013     }
3014 
3015     /**
3016      * RAF files contains a JPEG and a CFA data.
3017      * The JPEG contains two images, a preview and a thumbnail, while the CFA contains a RAW image.
3018      * This method looks at the first 160 bytes of a RAF file to retrieve the offset and length
3019      * values for the JPEG and CFA data.
3020      * Using that data, it parses the JPEG data to retrieve the preview and thumbnail image data,
3021      * then parses the CFA metadata to retrieve the primary image length/width values.
3022      * For data format details, see http://fileformats.archiveteam.org/wiki/Fujifilm_RAF
3023      */
getRafAttributes(ByteOrderedDataInputStream in)3024     private void getRafAttributes(ByteOrderedDataInputStream in) throws IOException {
3025         // Retrieve offset & length values
3026         in.skipBytes(RAF_OFFSET_TO_JPEG_IMAGE_OFFSET);
3027         byte[] jpegOffsetBytes = new byte[4];
3028         byte[] cfaHeaderOffsetBytes = new byte[4];
3029         in.read(jpegOffsetBytes);
3030         // Skip JPEG length value since it is not needed
3031         in.skipBytes(RAF_JPEG_LENGTH_VALUE_SIZE);
3032         in.read(cfaHeaderOffsetBytes);
3033         int rafJpegOffset = ByteBuffer.wrap(jpegOffsetBytes).getInt();
3034         int rafCfaHeaderOffset = ByteBuffer.wrap(cfaHeaderOffsetBytes).getInt();
3035 
3036         // Retrieve JPEG image metadata
3037         getJpegAttributes(in, rafJpegOffset, IFD_TYPE_PREVIEW);
3038 
3039         // Skip to CFA header offset.
3040         in.seek(rafCfaHeaderOffset);
3041 
3042         // Retrieve primary image length/width values, if TAG_RAF_IMAGE_SIZE exists
3043         in.setByteOrder(ByteOrder.BIG_ENDIAN);
3044         int numberOfDirectoryEntry = in.readInt();
3045         if (DEBUG) {
3046             Log.d(TAG, "numberOfDirectoryEntry: " + numberOfDirectoryEntry);
3047         }
3048         // CFA stores some metadata about the RAW image. Since CFA uses proprietary tags, can only
3049         // find and retrieve image size information tags, while skipping others.
3050         // See piex.cc RafGetDimension()
3051         for (int i = 0; i < numberOfDirectoryEntry; ++i) {
3052             int tagNumber = in.readUnsignedShort();
3053             int numberOfBytes = in.readUnsignedShort();
3054             if (tagNumber == TAG_RAF_IMAGE_SIZE.number) {
3055                 int imageLength = in.readShort();
3056                 int imageWidth = in.readShort();
3057                 ExifAttribute imageLengthAttribute =
3058                         ExifAttribute.createUShort(imageLength, mExifByteOrder);
3059                 ExifAttribute imageWidthAttribute =
3060                         ExifAttribute.createUShort(imageWidth, mExifByteOrder);
3061                 mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH, imageLengthAttribute);
3062                 mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH, imageWidthAttribute);
3063                 if (DEBUG) {
3064                     Log.d(TAG, "Updated to length: " + imageLength + ", width: " + imageWidth);
3065                 }
3066                 return;
3067             }
3068             in.skipBytes(numberOfBytes);
3069         }
3070     }
3071 
getHeifAttributes(ByteOrderedDataInputStream in)3072     private void getHeifAttributes(ByteOrderedDataInputStream in) throws IOException {
3073         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
3074         try {
3075             retriever.setDataSource(new MediaDataSource() {
3076                 long mPosition;
3077 
3078                 @Override
3079                 public void close() throws IOException {}
3080 
3081                 @Override
3082                 public int readAt(long position, byte[] buffer, int offset, int size)
3083                         throws IOException {
3084                     if (size == 0) {
3085                         return 0;
3086                     }
3087                     if (position < 0) {
3088                         return -1;
3089                     }
3090                     try {
3091                         if (mPosition != position) {
3092                             // We don't allow seek to positions after the available bytes,
3093                             // the input stream won't be able to seek back then.
3094                             // However, if we hit an exception before (mPosition set to -1),
3095                             // let it try the seek in hope it might recover.
3096                             if (mPosition >= 0 && position >= mPosition + in.available()) {
3097                                 return -1;
3098                             }
3099                             in.seek(position);
3100                             mPosition = position;
3101                         }
3102 
3103                         // If the read will cause us to go over the available bytes,
3104                         // reduce the size so that we stay in the available range.
3105                         // Otherwise the input stream may not be able to seek back.
3106                         if (size > in.available()) {
3107                             size = in.available();
3108                         }
3109 
3110                         int bytesRead = in.read(buffer, offset, size);
3111                         if (bytesRead >= 0) {
3112                             mPosition += bytesRead;
3113                             return bytesRead;
3114                         }
3115                     } catch (IOException e) {}
3116                     mPosition = -1; // need to seek on next read
3117                     return -1;
3118                 }
3119 
3120                 @Override
3121                 public long getSize() throws IOException {
3122                     return -1;
3123                 }
3124             });
3125 
3126             String exifOffsetStr = retriever.extractMetadata(
3127                     MediaMetadataRetriever.METADATA_KEY_EXIF_OFFSET);
3128             String exifLengthStr = retriever.extractMetadata(
3129                     MediaMetadataRetriever.METADATA_KEY_EXIF_LENGTH);
3130             String hasImage = retriever.extractMetadata(
3131                     MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
3132             String hasVideo = retriever.extractMetadata(
3133                     MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
3134 
3135             String width = null;
3136             String height = null;
3137             String rotation = null;
3138             final String METADATA_VALUE_YES = "yes";
3139             // If the file has both image and video, prefer image info over video info.
3140             // App querying ExifInterface is most likely using the bitmap path which
3141             // picks the image first.
3142             if (METADATA_VALUE_YES.equals(hasImage)) {
3143                 width = retriever.extractMetadata(
3144                         MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH);
3145                 height = retriever.extractMetadata(
3146                         MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT);
3147                 rotation = retriever.extractMetadata(
3148                         MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION);
3149             } else if (METADATA_VALUE_YES.equals(hasVideo)) {
3150                 width = retriever.extractMetadata(
3151                         MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
3152                 height = retriever.extractMetadata(
3153                         MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
3154                 rotation = retriever.extractMetadata(
3155                         MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
3156             }
3157 
3158             if (width != null) {
3159                 mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH,
3160                         ExifAttribute.createUShort(Integer.parseInt(width), mExifByteOrder));
3161             }
3162 
3163             if (height != null) {
3164                 mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH,
3165                         ExifAttribute.createUShort(Integer.parseInt(height), mExifByteOrder));
3166             }
3167 
3168             if (rotation != null) {
3169                 int orientation = ExifInterface.ORIENTATION_NORMAL;
3170 
3171                 // all rotation angles in CW
3172                 switch (Integer.parseInt(rotation)) {
3173                     case 90:
3174                         orientation = ExifInterface.ORIENTATION_ROTATE_90;
3175                         break;
3176                     case 180:
3177                         orientation = ExifInterface.ORIENTATION_ROTATE_180;
3178                         break;
3179                     case 270:
3180                         orientation = ExifInterface.ORIENTATION_ROTATE_270;
3181                         break;
3182                 }
3183 
3184                 mAttributes[IFD_TYPE_PRIMARY].put(TAG_ORIENTATION,
3185                         ExifAttribute.createUShort(orientation, mExifByteOrder));
3186             }
3187 
3188             if (exifOffsetStr != null && exifLengthStr != null) {
3189                 int offset = Integer.parseInt(exifOffsetStr);
3190                 int length = Integer.parseInt(exifLengthStr);
3191                 if (length <= 6) {
3192                     throw new IOException("Invalid exif length");
3193                 }
3194                 in.seek(offset);
3195                 byte[] identifier = new byte[6];
3196                 if (in.read(identifier) != 6) {
3197                     throw new IOException("Can't read identifier");
3198                 }
3199                 offset += 6;
3200                 length -= 6;
3201                 if (!Arrays.equals(identifier, IDENTIFIER_EXIF_APP1)) {
3202                     throw new IOException("Invalid identifier");
3203                 }
3204 
3205                 // TODO: Need to handle potential OutOfMemoryError
3206                 byte[] bytes = new byte[length];
3207                 if (in.read(bytes) != length) {
3208                     throw new IOException("Can't read exif");
3209                 }
3210                 // Save offset values for handling thumbnail and attribute offsets.
3211                 mExifOffset = offset;
3212                 readExifSegment(bytes, IFD_TYPE_PRIMARY);
3213             }
3214 
3215             String xmpOffsetStr = retriever.extractMetadata(
3216                     MediaMetadataRetriever.METADATA_KEY_XMP_OFFSET);
3217             String xmpLengthStr = retriever.extractMetadata(
3218                     MediaMetadataRetriever.METADATA_KEY_XMP_LENGTH);
3219             if (xmpOffsetStr != null && xmpLengthStr != null) {
3220                 int offset = Integer.parseInt(xmpOffsetStr);
3221                 int length = Integer.parseInt(xmpLengthStr);
3222                 in.seek(offset);
3223                 byte[] xmpBytes = new byte[length];
3224                 if (in.read(xmpBytes) != length) {
3225                     throw new IOException("Failed to read XMP from HEIF");
3226                 }
3227                 if (getAttribute(TAG_XMP) == null) {
3228                     mAttributes[IFD_TYPE_PRIMARY].put(TAG_XMP, new ExifAttribute(
3229                             IFD_FORMAT_BYTE, xmpBytes.length, offset, xmpBytes));
3230                 }
3231             }
3232 
3233             if (DEBUG) {
3234                 Log.d(TAG, "Heif meta: " + width + "x" + height + ", rotation " + rotation);
3235             }
3236         } finally {
3237             retriever.release();
3238         }
3239     }
3240 
getStandaloneAttributes(ByteOrderedDataInputStream in)3241     private void getStandaloneAttributes(ByteOrderedDataInputStream in) throws IOException {
3242         in.skipBytes(IDENTIFIER_EXIF_APP1.length);
3243         // TODO: Need to handle potential OutOfMemoryError
3244         byte[] data = new byte[in.available()];
3245         in.readFully(data);
3246         // Save offset values for handling thumbnail and attribute offsets.
3247         mExifOffset = IDENTIFIER_EXIF_APP1.length;
3248         readExifSegment(data, IFD_TYPE_PRIMARY);
3249     }
3250 
3251     /**
3252      * ORF files contains a primary image data and a MakerNote data that contains preview/thumbnail
3253      * images. Both data takes the form of IFDs and can therefore be read with the
3254      * readImageFileDirectory() method.
3255      * This method reads all the necessary data and updates the primary/preview/thumbnail image
3256      * information according to the GetOlympusPreviewImage() method in piex.cc.
3257      * For data format details, see the following:
3258      * http://fileformats.archiveteam.org/wiki/Olympus_ORF
3259      * https://libopenraw.freedesktop.org/wiki/Olympus_ORF
3260      */
getOrfAttributes(ByteOrderedDataInputStream in)3261     private void getOrfAttributes(ByteOrderedDataInputStream in) throws IOException {
3262         // Retrieve primary image data
3263         // Other Exif data will be located in the Makernote.
3264         getRawAttributes(in);
3265 
3266         // Additionally retrieve preview/thumbnail information from MakerNote tag, which contains
3267         // proprietary tags and therefore does not have offical documentation
3268         // See GetOlympusPreviewImage() in piex.cc & http://www.exiv2.org/tags-olympus.html
3269         ExifAttribute makerNoteAttribute =
3270                 (ExifAttribute) mAttributes[IFD_TYPE_EXIF].get(TAG_MAKER_NOTE);
3271         if (makerNoteAttribute != null) {
3272             // Create an ordered DataInputStream for MakerNote
3273             ByteOrderedDataInputStream makerNoteDataInputStream =
3274                     new ByteOrderedDataInputStream(makerNoteAttribute.bytes);
3275             makerNoteDataInputStream.setByteOrder(mExifByteOrder);
3276 
3277             // There are two types of headers for Olympus MakerNotes
3278             // See http://www.exiv2.org/makernote.html#R1
3279             byte[] makerNoteHeader1Bytes = new byte[ORF_MAKER_NOTE_HEADER_1.length];
3280             makerNoteDataInputStream.readFully(makerNoteHeader1Bytes);
3281             makerNoteDataInputStream.seek(0);
3282             byte[] makerNoteHeader2Bytes = new byte[ORF_MAKER_NOTE_HEADER_2.length];
3283             makerNoteDataInputStream.readFully(makerNoteHeader2Bytes);
3284             // Skip the corresponding amount of bytes for each header type
3285             if (Arrays.equals(makerNoteHeader1Bytes, ORF_MAKER_NOTE_HEADER_1)) {
3286                 makerNoteDataInputStream.seek(ORF_MAKER_NOTE_HEADER_1_SIZE);
3287             } else if (Arrays.equals(makerNoteHeader2Bytes, ORF_MAKER_NOTE_HEADER_2)) {
3288                 makerNoteDataInputStream.seek(ORF_MAKER_NOTE_HEADER_2_SIZE);
3289             }
3290 
3291             // Read IFD data from MakerNote
3292             readImageFileDirectory(makerNoteDataInputStream, IFD_TYPE_ORF_MAKER_NOTE);
3293 
3294             // Retrieve & update preview image offset & length values
3295             ExifAttribute imageLengthAttribute = (ExifAttribute)
3296                     mAttributes[IFD_TYPE_ORF_CAMERA_SETTINGS].get(TAG_ORF_PREVIEW_IMAGE_START);
3297             ExifAttribute bitsPerSampleAttribute = (ExifAttribute)
3298                     mAttributes[IFD_TYPE_ORF_CAMERA_SETTINGS].get(TAG_ORF_PREVIEW_IMAGE_LENGTH);
3299 
3300             if (imageLengthAttribute != null && bitsPerSampleAttribute != null) {
3301                 mAttributes[IFD_TYPE_PREVIEW].put(TAG_JPEG_INTERCHANGE_FORMAT,
3302                         imageLengthAttribute);
3303                 mAttributes[IFD_TYPE_PREVIEW].put(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
3304                         bitsPerSampleAttribute);
3305             }
3306 
3307             // TODO: Check this behavior in other ORF files
3308             // Retrieve primary image length & width values
3309             // See piex.cc GetOlympusPreviewImage()
3310             ExifAttribute aspectFrameAttribute = (ExifAttribute)
3311                     mAttributes[IFD_TYPE_ORF_IMAGE_PROCESSING].get(TAG_ORF_ASPECT_FRAME);
3312             if (aspectFrameAttribute != null) {
3313                 int[] aspectFrameValues = new int[4];
3314                 aspectFrameValues = (int[]) aspectFrameAttribute.getValue(mExifByteOrder);
3315                 if (aspectFrameValues[2] > aspectFrameValues[0] &&
3316                         aspectFrameValues[3] > aspectFrameValues[1]) {
3317                     int primaryImageWidth = aspectFrameValues[2] - aspectFrameValues[0] + 1;
3318                     int primaryImageLength = aspectFrameValues[3] - aspectFrameValues[1] + 1;
3319                     // Swap width & length values
3320                     if (primaryImageWidth < primaryImageLength) {
3321                         primaryImageWidth += primaryImageLength;
3322                         primaryImageLength = primaryImageWidth - primaryImageLength;
3323                         primaryImageWidth -= primaryImageLength;
3324                     }
3325                     ExifAttribute primaryImageWidthAttribute =
3326                             ExifAttribute.createUShort(primaryImageWidth, mExifByteOrder);
3327                     ExifAttribute primaryImageLengthAttribute =
3328                             ExifAttribute.createUShort(primaryImageLength, mExifByteOrder);
3329 
3330                     mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH, primaryImageWidthAttribute);
3331                     mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH, primaryImageLengthAttribute);
3332                 }
3333             }
3334         }
3335     }
3336 
3337     // RW2 contains the primary image data in IFD0 and the preview and/or thumbnail image data in
3338     // the JpgFromRaw tag
3339     // See https://libopenraw.freedesktop.org/wiki/Panasonic_RAW/ and piex.cc Rw2GetPreviewData()
getRw2Attributes(ByteOrderedDataInputStream in)3340     private void getRw2Attributes(ByteOrderedDataInputStream in) throws IOException {
3341         // Retrieve primary image data
3342         getRawAttributes(in);
3343 
3344         // Retrieve preview and/or thumbnail image data
3345         ExifAttribute jpgFromRawAttribute =
3346                 (ExifAttribute) mAttributes[IFD_TYPE_PRIMARY].get(TAG_RW2_JPG_FROM_RAW);
3347         if (jpgFromRawAttribute != null) {
3348             getJpegAttributes(in, mRw2JpgFromRawOffset, IFD_TYPE_PREVIEW);
3349         }
3350 
3351         // Set ISO tag value if necessary
3352         ExifAttribute rw2IsoAttribute =
3353                 (ExifAttribute) mAttributes[IFD_TYPE_PRIMARY].get(TAG_RW2_ISO);
3354         ExifAttribute exifIsoAttribute =
3355                 (ExifAttribute) mAttributes[IFD_TYPE_EXIF].get(TAG_ISO_SPEED_RATINGS);
3356         if (rw2IsoAttribute != null && exifIsoAttribute == null) {
3357             // Place this attribute only if it doesn't exist
3358             mAttributes[IFD_TYPE_EXIF].put(TAG_ISO_SPEED_RATINGS, rw2IsoAttribute);
3359         }
3360     }
3361 
3362     // PNG contains the EXIF data as a Special-Purpose Chunk
getPngAttributes(ByteOrderedDataInputStream in)3363     private void getPngAttributes(ByteOrderedDataInputStream in) throws IOException {
3364         if (DEBUG) {
3365             Log.d(TAG, "getPngAttributes starting with: " + in);
3366         }
3367 
3368         // PNG uses Big Endian by default.
3369         // See PNG (Portable Network Graphics) Specification, Version 1.2,
3370         // 2.1. Integers and byte order
3371         in.setByteOrder(ByteOrder.BIG_ENDIAN);
3372 
3373         int bytesRead = 0;
3374 
3375         // Skip the signature bytes
3376         in.skipBytes(PNG_SIGNATURE.length);
3377         bytesRead += PNG_SIGNATURE.length;
3378 
3379         // Each chunk is made up of four parts:
3380         //   1) Length: 4-byte unsigned integer indicating the number of bytes in the
3381         //   Chunk Data field. Excludes Chunk Type and CRC bytes.
3382         //   2) Chunk Type: 4-byte chunk type code.
3383         //   3) Chunk Data: The data bytes. Can be zero-length.
3384         //   4) CRC: 4-byte data calculated on the preceding bytes in the chunk. Always
3385         //   present.
3386         // --> 4 (length bytes) + 4 (type bytes) + X (data bytes) + 4 (CRC bytes)
3387         // See PNG (Portable Network Graphics) Specification, Version 1.2,
3388         // 3.2. Chunk layout
3389         try {
3390             while (true) {
3391                 int length = in.readInt();
3392                 bytesRead += 4;
3393 
3394                 byte[] type = new byte[PNG_CHUNK_TYPE_BYTE_LENGTH];
3395                 if (in.read(type) != type.length) {
3396                     throw new IOException("Encountered invalid length while parsing PNG chunk"
3397                             + "type");
3398                 }
3399                 bytesRead += PNG_CHUNK_TYPE_BYTE_LENGTH;
3400 
3401                 // The first chunk must be the IHDR chunk
3402                 if (bytesRead == 16 && !Arrays.equals(type, PNG_CHUNK_TYPE_IHDR)) {
3403                     throw new IOException("Encountered invalid PNG file--IHDR chunk should appear"
3404                             + "as the first chunk");
3405                 }
3406 
3407                 if (Arrays.equals(type, PNG_CHUNK_TYPE_IEND)) {
3408                     // IEND marks the end of the image.
3409                     break;
3410                 } else if (Arrays.equals(type, PNG_CHUNK_TYPE_EXIF)) {
3411                     // TODO: Need to handle potential OutOfMemoryError
3412                     byte[] data = new byte[length];
3413                     if (in.read(data) != length) {
3414                         throw new IOException("Failed to read given length for given PNG chunk "
3415                                 + "type: " + byteArrayToHexString(type));
3416                     }
3417 
3418                     // Compare CRC values for potential data corruption.
3419                     int dataCrcValue = in.readInt();
3420                     // Cyclic Redundancy Code used to check for corruption of the data
3421                     CRC32 crc = new CRC32();
3422                     crc.update(type);
3423                     crc.update(data);
3424                     if ((int) crc.getValue() != dataCrcValue) {
3425                         throw new IOException("Encountered invalid CRC value for PNG-EXIF chunk."
3426                                 + "\n recorded CRC value: " + dataCrcValue + ", calculated CRC "
3427                                 + "value: " + crc.getValue());
3428                     }
3429                     // Save offset values for handleThumbnailFromJfif() function
3430                     mExifOffset = bytesRead;
3431                     readExifSegment(data, IFD_TYPE_PRIMARY);
3432 
3433                     validateImages();
3434                     break;
3435                 } else {
3436                     // Skip to next chunk
3437                     in.skipBytes(length + PNG_CHUNK_CRC_BYTE_LENGTH);
3438                     bytesRead += length + PNG_CHUNK_CRC_BYTE_LENGTH;
3439                 }
3440             }
3441         } catch (EOFException e) {
3442             // Should not reach here. Will only reach here if the file is corrupted or
3443             // does not follow the PNG specifications
3444             throw new IOException("Encountered corrupt PNG file.");
3445         }
3446     }
3447 
3448     // WebP contains EXIF data as a RIFF File Format Chunk
3449     // All references below can be found in the following link.
3450     // https://developers.google.com/speed/webp/docs/riff_container
getWebpAttributes(ByteOrderedDataInputStream in)3451     private void getWebpAttributes(ByteOrderedDataInputStream in) throws IOException {
3452         if (DEBUG) {
3453             Log.d(TAG, "getWebpAttributes starting with: " + in);
3454         }
3455         // WebP uses little-endian by default.
3456         // See Section "Terminology & Basics"
3457         in.setByteOrder(ByteOrder.LITTLE_ENDIAN);
3458         in.skipBytes(WEBP_SIGNATURE_1.length);
3459         // File size corresponds to the size of the entire file from offset 8.
3460         // See Section "WebP File Header"
3461         int fileSize = in.readInt() + 8;
3462         int bytesRead = 8;
3463         bytesRead += in.skipBytes(WEBP_SIGNATURE_2.length);
3464         try {
3465             while (true) {
3466                 // TODO: Check the first Chunk Type, and if it is VP8X, check if the chunks are
3467                 // ordered properly.
3468 
3469                 // Each chunk is made up of three parts:
3470                 //   1) Chunk FourCC: 4-byte concatenating four ASCII characters.
3471                 //   2) Chunk Size: 4-byte unsigned integer indicating the size of the chunk.
3472                 //                  Excludes Chunk FourCC and Chunk Size bytes.
3473                 //   3) Chunk Payload: data payload. A single padding byte ('0') is added if
3474                 //                     Chunk Size is odd.
3475                 // See Section "RIFF File Format"
3476                 byte[] code = new byte[WEBP_CHUNK_TYPE_BYTE_LENGTH];
3477                 if (in.read(code) != code.length) {
3478                     throw new IOException("Encountered invalid length while parsing WebP chunk"
3479                             + "type");
3480                 }
3481                 bytesRead += 4;
3482                 int chunkSize = in.readInt();
3483                 bytesRead += 4;
3484                 if (Arrays.equals(WEBP_CHUNK_TYPE_EXIF, code)) {
3485                     // TODO: Need to handle potential OutOfMemoryError
3486                     byte[] payload = new byte[chunkSize];
3487                     if (in.read(payload) != chunkSize) {
3488                         throw new IOException("Failed to read given length for given PNG chunk "
3489                                 + "type: " + byteArrayToHexString(code));
3490                     }
3491                     // Save offset values for handling thumbnail and attribute offsets.
3492                     mExifOffset = bytesRead;
3493                     readExifSegment(payload, IFD_TYPE_PRIMARY);
3494 
3495                     // Save offset values for handleThumbnailFromJfif() function
3496                     mExifOffset = bytesRead;
3497                     break;
3498                 } else {
3499                     // Add a single padding byte at end if chunk size is odd
3500                     chunkSize = (chunkSize % 2 == 1) ? chunkSize + 1 : chunkSize;
3501                     // Check if skipping to next chunk is necessary
3502                     if (bytesRead + chunkSize == fileSize) {
3503                         // Reached end of file
3504                         break;
3505                     } else if (bytesRead + chunkSize > fileSize) {
3506                         throw new IOException("Encountered WebP file with invalid chunk size");
3507                     }
3508                     // Skip to next chunk
3509                     int skipped = in.skipBytes(chunkSize);
3510                     if (skipped != chunkSize) {
3511                         throw new IOException("Encountered WebP file with invalid chunk size");
3512                     }
3513                     bytesRead += skipped;
3514                 }
3515             }
3516         } catch (EOFException e) {
3517             // Should not reach here. Will only reach here if the file is corrupted or
3518             // does not follow the WebP specifications
3519             throw new IOException("Encountered corrupt WebP file.");
3520         }
3521     }
3522 
3523     // Stores a new JPEG image with EXIF attributes into a given output stream.
saveJpegAttributes(InputStream inputStream, OutputStream outputStream)3524     private void saveJpegAttributes(InputStream inputStream, OutputStream outputStream)
3525             throws IOException {
3526         // See JPEG File Interchange Format Specification, "JFIF Specification"
3527         if (DEBUG) {
3528             Log.d(TAG, "saveJpegAttributes starting with (inputStream: " + inputStream
3529                     + ", outputStream: " + outputStream + ")");
3530         }
3531         DataInputStream dataInputStream = new DataInputStream(inputStream);
3532         ByteOrderedDataOutputStream dataOutputStream =
3533                 new ByteOrderedDataOutputStream(outputStream, ByteOrder.BIG_ENDIAN);
3534         if (dataInputStream.readByte() != MARKER) {
3535             throw new IOException("Invalid marker");
3536         }
3537         dataOutputStream.writeByte(MARKER);
3538         if (dataInputStream.readByte() != MARKER_SOI) {
3539             throw new IOException("Invalid marker");
3540         }
3541         dataOutputStream.writeByte(MARKER_SOI);
3542 
3543         // Remove XMP data if it is from a separate marker (IDENTIFIER_XMP_APP1, not
3544         // IDENTIFIER_EXIF_APP1)
3545         // Will re-add it later after the rest of the file is written
3546         ExifAttribute xmpAttribute = null;
3547         if (getAttribute(TAG_XMP) != null && mXmpIsFromSeparateMarker) {
3548             xmpAttribute = (ExifAttribute) mAttributes[IFD_TYPE_PRIMARY].remove(TAG_XMP);
3549         }
3550 
3551         // Write EXIF APP1 segment
3552         dataOutputStream.writeByte(MARKER);
3553         dataOutputStream.writeByte(MARKER_APP1);
3554         writeExifSegment(dataOutputStream);
3555 
3556         // Re-add previously removed XMP data.
3557         if (xmpAttribute != null) {
3558             mAttributes[IFD_TYPE_PRIMARY].put(TAG_XMP, xmpAttribute);
3559         }
3560 
3561         byte[] bytes = new byte[4096];
3562 
3563         while (true) {
3564             byte marker = dataInputStream.readByte();
3565             if (marker != MARKER) {
3566                 throw new IOException("Invalid marker");
3567             }
3568             marker = dataInputStream.readByte();
3569             switch (marker) {
3570                 case MARKER_APP1: {
3571                     int length = dataInputStream.readUnsignedShort() - 2;
3572                     if (length < 0) {
3573                         throw new IOException("Invalid length");
3574                     }
3575                     byte[] identifier = new byte[6];
3576                     if (length >= 6) {
3577                         if (dataInputStream.read(identifier) != 6) {
3578                             throw new IOException("Invalid exif");
3579                         }
3580                         if (Arrays.equals(identifier, IDENTIFIER_EXIF_APP1)) {
3581                             // Skip the original EXIF APP1 segment.
3582                             if (dataInputStream.skipBytes(length - 6) != length - 6) {
3583                                 throw new IOException("Invalid length");
3584                             }
3585                             break;
3586                         }
3587                     }
3588                     // Copy non-EXIF APP1 segment.
3589                     dataOutputStream.writeByte(MARKER);
3590                     dataOutputStream.writeByte(marker);
3591                     dataOutputStream.writeUnsignedShort(length + 2);
3592                     if (length >= 6) {
3593                         length -= 6;
3594                         dataOutputStream.write(identifier);
3595                     }
3596                     int read;
3597                     while (length > 0 && (read = dataInputStream.read(
3598                             bytes, 0, Math.min(length, bytes.length))) >= 0) {
3599                         dataOutputStream.write(bytes, 0, read);
3600                         length -= read;
3601                     }
3602                     break;
3603                 }
3604                 case MARKER_EOI:
3605                 case MARKER_SOS: {
3606                     dataOutputStream.writeByte(MARKER);
3607                     dataOutputStream.writeByte(marker);
3608                     // Copy all the remaining data
3609                     copy(dataInputStream, dataOutputStream);
3610                     return;
3611                 }
3612                 default: {
3613                     // Copy JPEG segment
3614                     dataOutputStream.writeByte(MARKER);
3615                     dataOutputStream.writeByte(marker);
3616                     int length = dataInputStream.readUnsignedShort();
3617                     dataOutputStream.writeUnsignedShort(length);
3618                     length -= 2;
3619                     if (length < 0) {
3620                         throw new IOException("Invalid length");
3621                     }
3622                     int read;
3623                     while (length > 0 && (read = dataInputStream.read(
3624                             bytes, 0, Math.min(length, bytes.length))) >= 0) {
3625                         dataOutputStream.write(bytes, 0, read);
3626                         length -= read;
3627                     }
3628                     break;
3629                 }
3630             }
3631         }
3632     }
3633 
savePngAttributes(InputStream inputStream, OutputStream outputStream)3634     private void savePngAttributes(InputStream inputStream, OutputStream outputStream)
3635             throws IOException {
3636         if (DEBUG) {
3637             Log.d(TAG, "savePngAttributes starting with (inputStream: " + inputStream
3638                     + ", outputStream: " + outputStream + ")");
3639         }
3640         DataInputStream dataInputStream = new DataInputStream(inputStream);
3641         ByteOrderedDataOutputStream dataOutputStream =
3642                 new ByteOrderedDataOutputStream(outputStream, ByteOrder.BIG_ENDIAN);
3643         // Copy PNG signature bytes
3644         copy(dataInputStream, dataOutputStream, PNG_SIGNATURE.length);
3645         // EXIF chunk can appear anywhere between the first (IHDR) and last (IEND) chunks, except
3646         // between IDAT chunks.
3647         // Adhering to these rules,
3648         //   1) if EXIF chunk did not exist in the original file, it will be stored right after the
3649         //      first chunk,
3650         //   2) if EXIF chunk existed in the original file, it will be stored in the same location.
3651         if (mExifOffset == 0) {
3652             // Copy IHDR chunk bytes
3653             int ihdrChunkLength = dataInputStream.readInt();
3654             dataOutputStream.writeInt(ihdrChunkLength);
3655             copy(dataInputStream, dataOutputStream, PNG_CHUNK_TYPE_BYTE_LENGTH
3656                     + ihdrChunkLength + PNG_CHUNK_CRC_BYTE_LENGTH);
3657         } else {
3658             // Copy up until the point where EXIF chunk length information is stored.
3659             int copyLength = mExifOffset - PNG_SIGNATURE.length
3660                     - 4 /* PNG EXIF chunk length bytes */
3661                     - PNG_CHUNK_TYPE_BYTE_LENGTH;
3662             copy(dataInputStream, dataOutputStream, copyLength);
3663             // Skip to the start of the chunk after the EXIF chunk
3664             int exifChunkLength = dataInputStream.readInt();
3665             dataInputStream.skipBytes(PNG_CHUNK_TYPE_BYTE_LENGTH + exifChunkLength
3666                     + PNG_CHUNK_CRC_BYTE_LENGTH);
3667         }
3668         // Write EXIF data
3669         try (ByteArrayOutputStream exifByteArrayOutputStream = new ByteArrayOutputStream()) {
3670             // A byte array is needed to calculate the CRC value of this chunk which requires
3671             // the chunk type bytes and the chunk data bytes.
3672             ByteOrderedDataOutputStream exifDataOutputStream =
3673                     new ByteOrderedDataOutputStream(exifByteArrayOutputStream,
3674                             ByteOrder.BIG_ENDIAN);
3675             // Store Exif data in separate byte array
3676             writeExifSegment(exifDataOutputStream);
3677             byte[] exifBytes =
3678                     ((ByteArrayOutputStream) exifDataOutputStream.mOutputStream).toByteArray();
3679             // Write EXIF chunk data
3680             dataOutputStream.write(exifBytes);
3681             // Write EXIF chunk CRC
3682             CRC32 crc = new CRC32();
3683             crc.update(exifBytes, 4 /* skip length bytes */, exifBytes.length - 4);
3684             dataOutputStream.writeInt((int) crc.getValue());
3685         }
3686         // Copy the rest of the file
3687         copy(dataInputStream, dataOutputStream);
3688     }
3689 
3690     // A WebP file has a header and a series of chunks.
3691     // The header is composed of:
3692     //   "RIFF" + File Size + "WEBP"
3693     //
3694     // The structure of the chunks can be divided largely into two categories:
3695     //   1) Contains only image data,
3696     //   2) Contains image data and extra data.
3697     // In the first category, there is only one chunk: type "VP8" (compression with loss) or "VP8L"
3698     // (lossless compression).
3699     // In the second category, the first chunk will be of type "VP8X", which contains flags
3700     // indicating which extra data exist in later chunks. The proceeding chunks must conform to
3701     // the following order based on type (if they exist):
3702     //   Color Profile ("ICCP") + Animation Control Data ("ANIM") + Image Data ("VP8"/"VP8L")
3703     //   + Exif metadata ("EXIF") + XMP metadata ("XMP")
3704     //
3705     // And in order to have EXIF data, a WebP file must be of the second structure and thus follow
3706     // the following rules:
3707     //   1) "VP8X" chunk as the first chunk,
3708     //   2) flag for EXIF inside "VP8X" chunk set to 1, and
3709     //   3) contain the "EXIF" chunk in the correct order amongst other chunks.
3710     //
3711     // Based on these rules, this API will support three different cases depending on the contents
3712     // of the original file:
3713     //   1) "EXIF" chunk already exists
3714     //     -> replace it with the new "EXIF" chunk
3715     //   2) "EXIF" chunk does not exist and the first chunk is "VP8" or "VP8L"
3716     //     -> add "VP8X" before the "VP8"/"VP8L" chunk (with EXIF flag set to 1), and add new
3717     //     "EXIF" chunk after the "VP8"/"VP8L" chunk.
3718     //   3) "EXIF" chunk does not exist and the first chunk is "VP8X"
3719     //     -> set EXIF flag in "VP8X" chunk to 1, and add new "EXIF" chunk at the proper location.
3720     //
3721     // See https://developers.google.com/speed/webp/docs/riff_container for more details.
saveWebpAttributes(InputStream inputStream, OutputStream outputStream)3722     private void saveWebpAttributes(InputStream inputStream, OutputStream outputStream)
3723             throws IOException {
3724         if (DEBUG) {
3725             Log.d(TAG, "saveWebpAttributes starting with (inputStream: " + inputStream
3726                     + ", outputStream: " + outputStream + ")");
3727         }
3728         ByteOrderedDataInputStream totalInputStream =
3729                 new ByteOrderedDataInputStream(inputStream, ByteOrder.LITTLE_ENDIAN);
3730         ByteOrderedDataOutputStream totalOutputStream =
3731                 new ByteOrderedDataOutputStream(outputStream, ByteOrder.LITTLE_ENDIAN);
3732 
3733         // WebP signature
3734         copy(totalInputStream, totalOutputStream, WEBP_SIGNATURE_1.length);
3735         // File length will be written after all the chunks have been written
3736         totalInputStream.skipBytes(WEBP_FILE_SIZE_BYTE_LENGTH + WEBP_SIGNATURE_2.length);
3737 
3738         // Create a separate byte array to calculate file length
3739         ByteArrayOutputStream nonHeaderByteArrayOutputStream = null;
3740         try {
3741             nonHeaderByteArrayOutputStream = new ByteArrayOutputStream();
3742             ByteOrderedDataOutputStream nonHeaderOutputStream =
3743                     new ByteOrderedDataOutputStream(nonHeaderByteArrayOutputStream,
3744                             ByteOrder.LITTLE_ENDIAN);
3745 
3746             if (mExifOffset != 0) {
3747                 // EXIF chunk exists in the original file
3748                 // Tested by webp_with_exif.webp
3749                 int bytesRead = WEBP_SIGNATURE_1.length + WEBP_FILE_SIZE_BYTE_LENGTH
3750                         + WEBP_SIGNATURE_2.length;
3751                 copy(totalInputStream, nonHeaderOutputStream,
3752                         mExifOffset - bytesRead - WEBP_CHUNK_TYPE_BYTE_LENGTH
3753                                 - WEBP_CHUNK_SIZE_BYTE_LENGTH);
3754 
3755                 // Skip input stream to the end of the EXIF chunk
3756                 totalInputStream.skipBytes(WEBP_CHUNK_TYPE_BYTE_LENGTH);
3757                 int exifChunkLength = totalInputStream.readInt();
3758                 // RIFF chunks have a single padding byte at the end if the declared chunk size is
3759                 // odd.
3760                 if (exifChunkLength % 2 != 0) {
3761                     exifChunkLength++;
3762                 }
3763                 totalInputStream.skipBytes(exifChunkLength);
3764 
3765                 // Write new EXIF chunk to output stream
3766                 int exifSize = writeExifSegment(nonHeaderOutputStream);
3767             } else {
3768                 // EXIF chunk does not exist in the original file
3769                 byte[] firstChunkType = new byte[WEBP_CHUNK_TYPE_BYTE_LENGTH];
3770                 if (totalInputStream.read(firstChunkType) != firstChunkType.length) {
3771                     throw new IOException("Encountered invalid length while parsing WebP chunk "
3772                             + "type");
3773                 }
3774 
3775                 if (Arrays.equals(firstChunkType, WEBP_CHUNK_TYPE_VP8X)) {
3776                     // Original file already includes other extra data
3777                     int size = totalInputStream.readInt();
3778                     // WebP files have a single padding byte at the end if the chunk size is odd.
3779                     byte[] data = new byte[(size % 2) == 1 ? size + 1 : size];
3780                     totalInputStream.read(data);
3781 
3782                     // Set the EXIF flag to 1
3783                     data[0] = (byte) (data[0] | (1 << 3));
3784 
3785                     // Retrieve Animation flag--in order to check where EXIF data should start
3786                     boolean containsAnimation = ((data[0] >> 1) & 1) == 1;
3787 
3788                     // Write the original VP8X chunk
3789                     nonHeaderOutputStream.write(WEBP_CHUNK_TYPE_VP8X);
3790                     nonHeaderOutputStream.writeInt(size);
3791                     nonHeaderOutputStream.write(data);
3792 
3793                     // Animation control data is composed of 1 ANIM chunk and multiple ANMF
3794                     // chunks and since the image data (VP8/VP8L) chunks are included in the ANMF
3795                     // chunks, EXIF data should come after the last ANMF chunk.
3796                     // Also, because there is no value indicating the amount of ANMF chunks, we need
3797                     // to keep iterating through chunks until we either reach the end of the file or
3798                     // the XMP chunk (if it exists).
3799                     // Tested by webp_with_anim_without_exif.webp
3800                     if (containsAnimation) {
3801                         copyChunksUpToGivenChunkType(totalInputStream, nonHeaderOutputStream,
3802                                 WEBP_CHUNK_TYPE_ANIM, null);
3803 
3804                         while (true) {
3805                             byte[] type = new byte[WEBP_CHUNK_TYPE_BYTE_LENGTH];
3806                             int read = inputStream.read(type);
3807                             if (!Arrays.equals(type, WEBP_CHUNK_TYPE_ANMF)) {
3808                                 // Either we have reached EOF or the start of a non-ANMF chunk
3809                                 writeExifSegment(nonHeaderOutputStream);
3810                                 break;
3811                             }
3812                             copyWebPChunk(totalInputStream, nonHeaderOutputStream, type);
3813                         }
3814                     } else {
3815                         // Skip until we find the VP8 or VP8L chunk
3816                         copyChunksUpToGivenChunkType(totalInputStream, nonHeaderOutputStream,
3817                                 WEBP_CHUNK_TYPE_VP8, WEBP_CHUNK_TYPE_VP8L);
3818                         writeExifSegment(nonHeaderOutputStream);
3819                     }
3820                 } else if (Arrays.equals(firstChunkType, WEBP_CHUNK_TYPE_VP8)
3821                         || Arrays.equals(firstChunkType, WEBP_CHUNK_TYPE_VP8L)) {
3822                     int size = totalInputStream.readInt();
3823                     int bytesToRead = size;
3824                     // WebP files have a single padding byte at the end if the chunk size is odd.
3825                     if (size % 2 == 1) {
3826                         bytesToRead += 1;
3827                     }
3828 
3829                     // Retrieve image width/height
3830                     int widthAndHeight = 0;
3831                     int width = 0;
3832                     int height = 0;
3833                     boolean alpha = false;
3834                     // Save VP8 frame data for later
3835                     byte[] vp8Frame = new byte[3];
3836 
3837                     if (Arrays.equals(firstChunkType, WEBP_CHUNK_TYPE_VP8)) {
3838                         totalInputStream.read(vp8Frame);
3839 
3840                         // Check signature
3841                         byte[] vp8Signature = new byte[3];
3842                         if (totalInputStream.read(vp8Signature) != vp8Signature.length
3843                                 || !Arrays.equals(WEBP_VP8_SIGNATURE, vp8Signature)) {
3844                             throw new IOException("Encountered error while checking VP8 "
3845                                     + "signature");
3846                         }
3847 
3848                         // Retrieve image width/height
3849                         widthAndHeight = totalInputStream.readInt();
3850                         width = (widthAndHeight << 18) >> 18;
3851                         height = (widthAndHeight << 2) >> 18;
3852                         bytesToRead -= (vp8Frame.length + vp8Signature.length + 4);
3853                     } else if (Arrays.equals(firstChunkType, WEBP_CHUNK_TYPE_VP8L)) {
3854                         // Check signature
3855                         byte vp8lSignature = totalInputStream.readByte();
3856                         if (vp8lSignature != WEBP_VP8L_SIGNATURE) {
3857                             throw new IOException("Encountered error while checking VP8L "
3858                                     + "signature");
3859                         }
3860 
3861                         // Retrieve image width/height
3862                         widthAndHeight = totalInputStream.readInt();
3863                         // VP8L stores width - 1 and height - 1 values. See "2 RIFF Header" of
3864                         // "WebP Lossless Bitstream Specification"
3865                         width = ((widthAndHeight << 18) >> 18) + 1;
3866                         height = ((widthAndHeight << 4) >> 18) + 1;
3867                         // Retrieve alpha bit
3868                         alpha = (widthAndHeight & (1 << 28)) != 0;
3869                         bytesToRead -= (1 /* VP8L signature */ + 4);
3870                     }
3871 
3872                     // Create VP8X with Exif flag set to 1
3873                     nonHeaderOutputStream.write(WEBP_CHUNK_TYPE_VP8X);
3874                     nonHeaderOutputStream.writeInt(WEBP_CHUNK_TYPE_VP8X_DEFAULT_LENGTH);
3875                     byte[] data = new byte[WEBP_CHUNK_TYPE_VP8X_DEFAULT_LENGTH];
3876                     // ALPHA flag
3877                     if (alpha) {
3878                         data[0] = (byte) (data[0] | (1 << 4));
3879                     }
3880                     // EXIF flag
3881                     data[0] = (byte) (data[0] | (1 << 3));
3882                     // VP8X stores Width - 1 and Height - 1 values
3883                     width -= 1;
3884                     height -= 1;
3885                     data[4] = (byte) width;
3886                     data[5] = (byte) (width >> 8);
3887                     data[6] = (byte) (width >> 16);
3888                     data[7] = (byte) height;
3889                     data[8] = (byte) (height >> 8);
3890                     data[9] = (byte) (height >> 16);
3891                     nonHeaderOutputStream.write(data);
3892 
3893                     // Write VP8 or VP8L data
3894                     nonHeaderOutputStream.write(firstChunkType);
3895                     nonHeaderOutputStream.writeInt(size);
3896                     if (Arrays.equals(firstChunkType, WEBP_CHUNK_TYPE_VP8)) {
3897                         nonHeaderOutputStream.write(vp8Frame);
3898                         nonHeaderOutputStream.write(WEBP_VP8_SIGNATURE);
3899                         nonHeaderOutputStream.writeInt(widthAndHeight);
3900                     } else if (Arrays.equals(firstChunkType, WEBP_CHUNK_TYPE_VP8L)) {
3901                         nonHeaderOutputStream.write(WEBP_VP8L_SIGNATURE);
3902                         nonHeaderOutputStream.writeInt(widthAndHeight);
3903                     }
3904                     copy(totalInputStream, nonHeaderOutputStream, bytesToRead);
3905 
3906                     // Write EXIF chunk
3907                     writeExifSegment(nonHeaderOutputStream);
3908                 }
3909             }
3910 
3911             // Copy the rest of the file
3912             copy(totalInputStream, nonHeaderOutputStream);
3913 
3914             // Write file length + second signature
3915             totalOutputStream.writeInt(nonHeaderByteArrayOutputStream.size()
3916                     + WEBP_SIGNATURE_2.length);
3917             totalOutputStream.write(WEBP_SIGNATURE_2);
3918             nonHeaderByteArrayOutputStream.writeTo(totalOutputStream);
3919         } catch (Exception e) {
3920             throw new IOException("Failed to save WebP file", e);
3921         } finally {
3922             closeQuietly(nonHeaderByteArrayOutputStream);
3923         }
3924     }
3925 
copyChunksUpToGivenChunkType(ByteOrderedDataInputStream inputStream, ByteOrderedDataOutputStream outputStream, byte[] firstGivenType, byte[] secondGivenType)3926     private void copyChunksUpToGivenChunkType(ByteOrderedDataInputStream inputStream,
3927             ByteOrderedDataOutputStream outputStream, byte[] firstGivenType,
3928             byte[] secondGivenType) throws IOException {
3929         while (true) {
3930             byte[] type = new byte[WEBP_CHUNK_TYPE_BYTE_LENGTH];
3931             if (inputStream.read(type) != type.length) {
3932                 throw new IOException("Encountered invalid length while copying WebP chunks up to"
3933                         + "chunk type " + new String(firstGivenType, ASCII)
3934                         + ((secondGivenType == null) ? "" : " or " + new String(secondGivenType,
3935                         ASCII)));
3936             }
3937             copyWebPChunk(inputStream, outputStream, type);
3938             if (Arrays.equals(type, firstGivenType)
3939                     || (secondGivenType != null && Arrays.equals(type, secondGivenType))) {
3940                 break;
3941             }
3942         }
3943     }
3944 
copyWebPChunk(ByteOrderedDataInputStream inputStream, ByteOrderedDataOutputStream outputStream, byte[] type)3945     private void copyWebPChunk(ByteOrderedDataInputStream inputStream,
3946             ByteOrderedDataOutputStream outputStream, byte[] type) throws IOException {
3947         int size = inputStream.readInt();
3948         outputStream.write(type);
3949         outputStream.writeInt(size);
3950         // WebP files have a single padding byte at the end if the chunk size is odd.
3951         copy(inputStream, outputStream, (size % 2) == 1 ? size + 1 : size);
3952     }
3953 
3954     // Reads the given EXIF byte area and save its tag data into attributes.
readExifSegment(byte[] exifBytes, int imageType)3955     private void readExifSegment(byte[] exifBytes, int imageType) throws IOException {
3956         ByteOrderedDataInputStream dataInputStream =
3957                 new ByteOrderedDataInputStream(exifBytes);
3958 
3959         // Parse TIFF Headers. See JEITA CP-3451C Section 4.5.2. Table 1.
3960         parseTiffHeaders(dataInputStream, exifBytes.length);
3961 
3962         // Read TIFF image file directories. See JEITA CP-3451C Section 4.5.2. Figure 6.
3963         readImageFileDirectory(dataInputStream, imageType);
3964     }
3965 
addDefaultValuesForCompatibility()3966     private void addDefaultValuesForCompatibility() {
3967         // If DATETIME tag has no value, then set the value to DATETIME_ORIGINAL tag's.
3968         String valueOfDateTimeOriginal = getAttribute(TAG_DATETIME_ORIGINAL);
3969         if (valueOfDateTimeOriginal != null && getAttribute(TAG_DATETIME) == null) {
3970             mAttributes[IFD_TYPE_PRIMARY].put(TAG_DATETIME,
3971                     ExifAttribute.createString(valueOfDateTimeOriginal));
3972         }
3973 
3974         // Add the default value.
3975         if (getAttribute(TAG_IMAGE_WIDTH) == null) {
3976             mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH,
3977                     ExifAttribute.createULong(0, mExifByteOrder));
3978         }
3979         if (getAttribute(TAG_IMAGE_LENGTH) == null) {
3980             mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH,
3981                     ExifAttribute.createULong(0, mExifByteOrder));
3982         }
3983         if (getAttribute(TAG_ORIENTATION) == null) {
3984             mAttributes[IFD_TYPE_PRIMARY].put(TAG_ORIENTATION,
3985                     ExifAttribute.createUShort(0, mExifByteOrder));
3986         }
3987         if (getAttribute(TAG_LIGHT_SOURCE) == null) {
3988             mAttributes[IFD_TYPE_EXIF].put(TAG_LIGHT_SOURCE,
3989                     ExifAttribute.createULong(0, mExifByteOrder));
3990         }
3991     }
3992 
readByteOrder(ByteOrderedDataInputStream dataInputStream)3993     private ByteOrder readByteOrder(ByteOrderedDataInputStream dataInputStream)
3994             throws IOException {
3995         // Read byte order.
3996         short byteOrder = dataInputStream.readShort();
3997         switch (byteOrder) {
3998             case BYTE_ALIGN_II:
3999                 if (DEBUG) {
4000                     Log.d(TAG, "readExifSegment: Byte Align II");
4001                 }
4002                 return ByteOrder.LITTLE_ENDIAN;
4003             case BYTE_ALIGN_MM:
4004                 if (DEBUG) {
4005                     Log.d(TAG, "readExifSegment: Byte Align MM");
4006                 }
4007                 return ByteOrder.BIG_ENDIAN;
4008             default:
4009                 throw new IOException("Invalid byte order: " + Integer.toHexString(byteOrder));
4010         }
4011     }
4012 
parseTiffHeaders(ByteOrderedDataInputStream dataInputStream, int exifBytesLength)4013     private void parseTiffHeaders(ByteOrderedDataInputStream dataInputStream,
4014             int exifBytesLength) throws IOException {
4015         // Read byte order
4016         mExifByteOrder = readByteOrder(dataInputStream);
4017         // Set byte order
4018         dataInputStream.setByteOrder(mExifByteOrder);
4019 
4020         // Check start code
4021         int startCode = dataInputStream.readUnsignedShort();
4022         if (mMimeType != IMAGE_TYPE_ORF && mMimeType != IMAGE_TYPE_RW2 && startCode != START_CODE) {
4023             throw new IOException("Invalid start code: " + Integer.toHexString(startCode));
4024         }
4025 
4026         // Read and skip to first ifd offset
4027         int firstIfdOffset = dataInputStream.readInt();
4028         if (firstIfdOffset < 8 || firstIfdOffset >= exifBytesLength) {
4029             throw new IOException("Invalid first Ifd offset: " + firstIfdOffset);
4030         }
4031         firstIfdOffset -= 8;
4032         if (firstIfdOffset > 0) {
4033             if (dataInputStream.skipBytes(firstIfdOffset) != firstIfdOffset) {
4034                 throw new IOException("Couldn't jump to first Ifd: " + firstIfdOffset);
4035             }
4036         }
4037     }
4038 
4039     // Reads image file directory, which is a tag group in EXIF.
readImageFileDirectory(ByteOrderedDataInputStream dataInputStream, @IfdType int ifdType)4040     private void readImageFileDirectory(ByteOrderedDataInputStream dataInputStream,
4041             @IfdType int ifdType) throws IOException {
4042         // Save offset of current IFD to prevent reading an IFD that is already read.
4043         mHandledIfdOffsets.add(dataInputStream.mPosition);
4044 
4045         if (dataInputStream.mPosition + 2 > dataInputStream.mLength) {
4046             // Return if there is no data from the offset.
4047             return;
4048         }
4049         // See TIFF 6.0 Section 2: TIFF Structure, Figure 1.
4050         short numberOfDirectoryEntry = dataInputStream.readShort();
4051         if (dataInputStream.mPosition + 12 * numberOfDirectoryEntry > dataInputStream.mLength
4052                 || numberOfDirectoryEntry <= 0) {
4053             // Return if the size of entries is either too big or negative.
4054             return;
4055         }
4056 
4057         if (DEBUG) {
4058             Log.d(TAG, "numberOfDirectoryEntry: " + numberOfDirectoryEntry);
4059         }
4060 
4061         // See TIFF 6.0 Section 2: TIFF Structure, "Image File Directory".
4062         for (short i = 0; i < numberOfDirectoryEntry; ++i) {
4063             int tagNumber = dataInputStream.readUnsignedShort();
4064             int dataFormat = dataInputStream.readUnsignedShort();
4065             int numberOfComponents = dataInputStream.readInt();
4066             // Next four bytes is for data offset or value.
4067             long nextEntryOffset = dataInputStream.peek() + 4;
4068 
4069             // Look up a corresponding tag from tag number
4070             ExifTag tag = (ExifTag) sExifTagMapsForReading[ifdType].get(tagNumber);
4071 
4072             if (DEBUG) {
4073                 Log.d(TAG, String.format("ifdType: %d, tagNumber: %d, tagName: %s, dataFormat: %d, "
4074                         + "numberOfComponents: %d", ifdType, tagNumber,
4075                         tag != null ? tag.name : null, dataFormat, numberOfComponents));
4076             }
4077 
4078             long byteCount = 0;
4079             boolean valid = false;
4080             if (tag == null) {
4081                 if (DEBUG) {
4082                     Log.d(TAG, "Skip the tag entry since tag number is not defined: " + tagNumber);
4083                 }
4084             } else if (dataFormat <= 0 || dataFormat >= IFD_FORMAT_BYTES_PER_FORMAT.length) {
4085                 if (DEBUG) {
4086                     Log.d(TAG, "Skip the tag entry since data format is invalid: " + dataFormat);
4087                 }
4088             } else {
4089                 byteCount = (long) numberOfComponents * IFD_FORMAT_BYTES_PER_FORMAT[dataFormat];
4090                 if (byteCount < 0 || byteCount > Integer.MAX_VALUE) {
4091                     if (DEBUG) {
4092                         Log.d(TAG, "Skip the tag entry since the number of components is invalid: "
4093                                 + numberOfComponents);
4094                     }
4095                 } else {
4096                     valid = true;
4097                 }
4098             }
4099             if (!valid) {
4100                 dataInputStream.seek(nextEntryOffset);
4101                 continue;
4102             }
4103 
4104             // Read a value from data field or seek to the value offset which is stored in data
4105             // field if the size of the entry value is bigger than 4.
4106             if (byteCount > 4) {
4107                 int offset = dataInputStream.readInt();
4108                 if (DEBUG) {
4109                     Log.d(TAG, "seek to data offset: " + offset);
4110                 }
4111                 if (mMimeType == IMAGE_TYPE_ORF) {
4112                     if (tag.name == TAG_MAKER_NOTE) {
4113                         // Save offset value for reading thumbnail
4114                         mOrfMakerNoteOffset = offset;
4115                     } else if (ifdType == IFD_TYPE_ORF_MAKER_NOTE
4116                             && tag.name == TAG_ORF_THUMBNAIL_IMAGE) {
4117                         // Retrieve & update values for thumbnail offset and length values for ORF
4118                         mOrfThumbnailOffset = offset;
4119                         mOrfThumbnailLength = numberOfComponents;
4120 
4121                         ExifAttribute compressionAttribute =
4122                                 ExifAttribute.createUShort(DATA_JPEG, mExifByteOrder);
4123                         ExifAttribute jpegInterchangeFormatAttribute =
4124                                 ExifAttribute.createULong(mOrfThumbnailOffset, mExifByteOrder);
4125                         ExifAttribute jpegInterchangeFormatLengthAttribute =
4126                                 ExifAttribute.createULong(mOrfThumbnailLength, mExifByteOrder);
4127 
4128                         mAttributes[IFD_TYPE_THUMBNAIL].put(TAG_COMPRESSION, compressionAttribute);
4129                         mAttributes[IFD_TYPE_THUMBNAIL].put(TAG_JPEG_INTERCHANGE_FORMAT,
4130                                 jpegInterchangeFormatAttribute);
4131                         mAttributes[IFD_TYPE_THUMBNAIL].put(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
4132                                 jpegInterchangeFormatLengthAttribute);
4133                     }
4134                 } else if (mMimeType == IMAGE_TYPE_RW2) {
4135                     if (tag.name == TAG_RW2_JPG_FROM_RAW) {
4136                         mRw2JpgFromRawOffset = offset;
4137                     }
4138                 }
4139                 if (offset + byteCount <= dataInputStream.mLength) {
4140                     dataInputStream.seek(offset);
4141                 } else {
4142                     // Skip if invalid data offset.
4143                     if (DEBUG) {
4144                         Log.d(TAG, "Skip the tag entry since data offset is invalid: " + offset);
4145                     }
4146                     dataInputStream.seek(nextEntryOffset);
4147                     continue;
4148                 }
4149             }
4150 
4151             // Recursively parse IFD when a IFD pointer tag appears.
4152             Integer nextIfdType = sExifPointerTagMap.get(tagNumber);
4153             if (DEBUG) {
4154                 Log.d(TAG, "nextIfdType: " + nextIfdType + " byteCount: " + byteCount);
4155             }
4156 
4157             if (nextIfdType != null) {
4158                 long offset = -1L;
4159                 // Get offset from data field
4160                 switch (dataFormat) {
4161                     case IFD_FORMAT_USHORT: {
4162                         offset = dataInputStream.readUnsignedShort();
4163                         break;
4164                     }
4165                     case IFD_FORMAT_SSHORT: {
4166                         offset = dataInputStream.readShort();
4167                         break;
4168                     }
4169                     case IFD_FORMAT_ULONG: {
4170                         offset = dataInputStream.readUnsignedInt();
4171                         break;
4172                     }
4173                     case IFD_FORMAT_SLONG:
4174                     case IFD_FORMAT_IFD: {
4175                         offset = dataInputStream.readInt();
4176                         break;
4177                     }
4178                     default: {
4179                         // Nothing to do
4180                         break;
4181                     }
4182                 }
4183                 if (DEBUG) {
4184                     Log.d(TAG, String.format("Offset: %d, tagName: %s", offset, tag.name));
4185                 }
4186 
4187                 // Check if the next IFD offset
4188                 // 1. Exists within the boundaries of the input stream
4189                 // 2. Does not point to a previously read IFD.
4190                 if (offset > 0L && offset < dataInputStream.mLength) {
4191                     if (!mHandledIfdOffsets.contains((int) offset)) {
4192                         dataInputStream.seek(offset);
4193                         readImageFileDirectory(dataInputStream, nextIfdType);
4194                     } else {
4195                         if (DEBUG) {
4196                             Log.d(TAG, "Skip jump into the IFD since it has already been read: "
4197                                     + "IfdType " + nextIfdType + " (at " + offset + ")");
4198                         }
4199                     }
4200                 } else {
4201                     if (DEBUG) {
4202                         Log.d(TAG, "Skip jump into the IFD since its offset is invalid: " + offset);
4203                     }
4204                 }
4205 
4206                 dataInputStream.seek(nextEntryOffset);
4207                 continue;
4208             }
4209 
4210             final int bytesOffset = dataInputStream.peek() + mExifOffset;
4211             final byte[] bytes = new byte[(int) byteCount];
4212             dataInputStream.readFully(bytes);
4213             ExifAttribute attribute = new ExifAttribute(dataFormat, numberOfComponents,
4214                     bytesOffset, bytes);
4215             mAttributes[ifdType].put(tag.name, attribute);
4216 
4217             // DNG files have a DNG Version tag specifying the version of specifications that the
4218             // image file is following.
4219             // See http://fileformats.archiveteam.org/wiki/DNG
4220             if (tag.name == TAG_DNG_VERSION) {
4221                 mMimeType = IMAGE_TYPE_DNG;
4222             }
4223 
4224             // PEF files have a Make or Model tag that begins with "PENTAX" or a compression tag
4225             // that is 65535.
4226             // See http://fileformats.archiveteam.org/wiki/Pentax_PEF
4227             if (((tag.name == TAG_MAKE || tag.name == TAG_MODEL)
4228                     && attribute.getStringValue(mExifByteOrder).contains(PEF_SIGNATURE))
4229                     || (tag.name == TAG_COMPRESSION
4230                     && attribute.getIntValue(mExifByteOrder) == 65535)) {
4231                 mMimeType = IMAGE_TYPE_PEF;
4232             }
4233 
4234             // Seek to next tag offset
4235             if (dataInputStream.peek() != nextEntryOffset) {
4236                 dataInputStream.seek(nextEntryOffset);
4237             }
4238         }
4239 
4240         if (dataInputStream.peek() + 4 <= dataInputStream.mLength) {
4241             int nextIfdOffset = dataInputStream.readInt();
4242             if (DEBUG) {
4243                 Log.d(TAG, String.format("nextIfdOffset: %d", nextIfdOffset));
4244             }
4245             // Check if the next IFD offset
4246             // 1. Exists within the boundaries of the input stream
4247             // 2. Does not point to a previously read IFD.
4248             if (nextIfdOffset > 0L && nextIfdOffset < dataInputStream.mLength) {
4249                 if (!mHandledIfdOffsets.contains(nextIfdOffset)) {
4250                     dataInputStream.seek(nextIfdOffset);
4251                     // Do not overwrite thumbnail IFD data if it alreay exists.
4252                     if (mAttributes[IFD_TYPE_THUMBNAIL].isEmpty()) {
4253                         readImageFileDirectory(dataInputStream, IFD_TYPE_THUMBNAIL);
4254                     } else if (mAttributes[IFD_TYPE_PREVIEW].isEmpty()) {
4255                         readImageFileDirectory(dataInputStream, IFD_TYPE_PREVIEW);
4256                     }
4257                 } else {
4258                     if (DEBUG) {
4259                         Log.d(TAG, "Stop reading file since re-reading an IFD may cause an "
4260                                 + "infinite loop: " + nextIfdOffset);
4261                     }
4262                 }
4263             } else {
4264                 if (DEBUG) {
4265                     Log.d(TAG, "Stop reading file since a wrong offset may cause an infinite loop: "
4266                             + nextIfdOffset);
4267                 }
4268             }
4269         }
4270     }
4271 
4272     /**
4273      * JPEG compressed images do not contain IMAGE_LENGTH & IMAGE_WIDTH tags.
4274      * This value uses JpegInterchangeFormat(JPEG data offset) value, and calls getJpegAttributes()
4275      * to locate SOF(Start of Frame) marker and update the image length & width values.
4276      * See JEITA CP-3451C Table 5 and Section 4.8.1. B.
4277      */
retrieveJpegImageSize(ByteOrderedDataInputStream in, int imageType)4278     private void retrieveJpegImageSize(ByteOrderedDataInputStream in, int imageType)
4279             throws IOException {
4280         // Check if image already has IMAGE_LENGTH & IMAGE_WIDTH values
4281         ExifAttribute imageLengthAttribute =
4282                 (ExifAttribute) mAttributes[imageType].get(TAG_IMAGE_LENGTH);
4283         ExifAttribute imageWidthAttribute =
4284                 (ExifAttribute) mAttributes[imageType].get(TAG_IMAGE_WIDTH);
4285 
4286         if (imageLengthAttribute == null || imageWidthAttribute == null) {
4287             // Find if offset for JPEG data exists
4288             ExifAttribute jpegInterchangeFormatAttribute =
4289                     (ExifAttribute) mAttributes[imageType].get(TAG_JPEG_INTERCHANGE_FORMAT);
4290             if (jpegInterchangeFormatAttribute != null) {
4291                 int jpegInterchangeFormat =
4292                         jpegInterchangeFormatAttribute.getIntValue(mExifByteOrder);
4293 
4294                 // Searches for SOF marker in JPEG data and updates IMAGE_LENGTH & IMAGE_WIDTH tags
4295                 getJpegAttributes(in, jpegInterchangeFormat, imageType);
4296             }
4297         }
4298     }
4299 
4300     // Sets thumbnail offset & length attributes based on JpegInterchangeFormat or StripOffsets tags
setThumbnailData(ByteOrderedDataInputStream in)4301     private void setThumbnailData(ByteOrderedDataInputStream in) throws IOException {
4302         HashMap thumbnailData = mAttributes[IFD_TYPE_THUMBNAIL];
4303 
4304         ExifAttribute compressionAttribute =
4305                 (ExifAttribute) thumbnailData.get(TAG_COMPRESSION);
4306         if (compressionAttribute != null) {
4307             mThumbnailCompression = compressionAttribute.getIntValue(mExifByteOrder);
4308             switch (mThumbnailCompression) {
4309                 case DATA_JPEG: {
4310                     handleThumbnailFromJfif(in, thumbnailData);
4311                     break;
4312                 }
4313                 case DATA_UNCOMPRESSED:
4314                 case DATA_JPEG_COMPRESSED: {
4315                     if (isSupportedDataType(thumbnailData)) {
4316                         handleThumbnailFromStrips(in, thumbnailData);
4317                     }
4318                     break;
4319                 }
4320             }
4321         } else {
4322             // Thumbnail data may not contain Compression tag value
4323             handleThumbnailFromJfif(in, thumbnailData);
4324         }
4325     }
4326 
4327     // Check JpegInterchangeFormat(JFIF) tags to retrieve thumbnail offset & length values
4328     // and reads the corresponding bytes if stream does not support seek function
handleThumbnailFromJfif(ByteOrderedDataInputStream in, HashMap thumbnailData)4329     private void handleThumbnailFromJfif(ByteOrderedDataInputStream in, HashMap thumbnailData)
4330             throws IOException {
4331         ExifAttribute jpegInterchangeFormatAttribute =
4332                 (ExifAttribute) thumbnailData.get(TAG_JPEG_INTERCHANGE_FORMAT);
4333         ExifAttribute jpegInterchangeFormatLengthAttribute =
4334                 (ExifAttribute) thumbnailData.get(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
4335         if (jpegInterchangeFormatAttribute != null
4336                 && jpegInterchangeFormatLengthAttribute != null) {
4337             int thumbnailOffset = jpegInterchangeFormatAttribute.getIntValue(mExifByteOrder);
4338             int thumbnailLength = jpegInterchangeFormatLengthAttribute.getIntValue(mExifByteOrder);
4339 
4340             if (mMimeType == IMAGE_TYPE_ORF) {
4341                 // Update offset value since RAF files have IFD data preceding MakerNote data.
4342                 thumbnailOffset += mOrfMakerNoteOffset;
4343             }
4344             // The following code limits the size of thumbnail size not to overflow EXIF data area.
4345             thumbnailLength = Math.min(thumbnailLength, in.getLength() - thumbnailOffset);
4346 
4347             if (thumbnailOffset > 0 && thumbnailLength > 0) {
4348                 mHasThumbnail = true;
4349                 // Need to add mExifOffset, which is the offset to the EXIF data segment
4350                 mThumbnailOffset = thumbnailOffset + mExifOffset;
4351                 mThumbnailLength = thumbnailLength;
4352                 mThumbnailCompression = DATA_JPEG;
4353 
4354                 if (mFilename == null && mAssetInputStream == null
4355                         && mSeekableFileDescriptor == null) {
4356                     // TODO: Need to handle potential OutOfMemoryError
4357                     // Save the thumbnail in memory if the input doesn't support reading again.
4358                     byte[] thumbnailBytes = new byte[mThumbnailLength];
4359                     in.seek(mThumbnailOffset);
4360                     in.readFully(thumbnailBytes);
4361                     mThumbnailBytes = thumbnailBytes;
4362                 }
4363             }
4364             if (DEBUG) {
4365                 Log.d(TAG, "Setting thumbnail attributes with offset: " + thumbnailOffset
4366                         + ", length: " + thumbnailLength);
4367             }
4368         }
4369     }
4370 
4371     // Check StripOffsets & StripByteCounts tags to retrieve thumbnail offset & length values
handleThumbnailFromStrips(ByteOrderedDataInputStream in, HashMap thumbnailData)4372     private void handleThumbnailFromStrips(ByteOrderedDataInputStream in, HashMap thumbnailData)
4373             throws IOException {
4374         ExifAttribute stripOffsetsAttribute =
4375                 (ExifAttribute) thumbnailData.get(TAG_STRIP_OFFSETS);
4376         ExifAttribute stripByteCountsAttribute =
4377                 (ExifAttribute) thumbnailData.get(TAG_STRIP_BYTE_COUNTS);
4378 
4379         if (stripOffsetsAttribute != null && stripByteCountsAttribute != null) {
4380             long[] stripOffsets =
4381                     convertToLongArray(stripOffsetsAttribute.getValue(mExifByteOrder));
4382             long[] stripByteCounts =
4383                     convertToLongArray(stripByteCountsAttribute.getValue(mExifByteOrder));
4384 
4385             if (stripOffsets == null || stripOffsets.length == 0) {
4386                 Log.w(TAG, "stripOffsets should not be null or have zero length.");
4387                 return;
4388             }
4389             if (stripByteCounts == null || stripByteCounts.length == 0) {
4390                 Log.w(TAG, "stripByteCounts should not be null or have zero length.");
4391                 return;
4392             }
4393             if (stripOffsets.length != stripByteCounts.length) {
4394                 Log.w(TAG, "stripOffsets and stripByteCounts should have same length.");
4395                 return;
4396             }
4397 
4398             // TODO: Need to handle potential OutOfMemoryError
4399             // Set thumbnail byte array data for non-consecutive strip bytes
4400             byte[] totalStripBytes =
4401                     new byte[(int) Arrays.stream(stripByteCounts).sum()];
4402 
4403             int bytesRead = 0;
4404             int bytesAdded = 0;
4405             mHasThumbnail = mHasThumbnailStrips = mAreThumbnailStripsConsecutive = true;
4406             for (int i = 0; i < stripOffsets.length; i++) {
4407                 int stripOffset = (int) stripOffsets[i];
4408                 int stripByteCount = (int) stripByteCounts[i];
4409 
4410                 // Check if strips are consecutive
4411                 // TODO: Add test for non-consecutive thumbnail image
4412                 if (i < stripOffsets.length - 1
4413                         && stripOffset + stripByteCount != stripOffsets[i + 1]) {
4414                     mAreThumbnailStripsConsecutive = false;
4415                 }
4416 
4417                 // Skip to offset
4418                 int skipBytes = stripOffset - bytesRead;
4419                 if (skipBytes < 0) {
4420                     Log.d(TAG, "Invalid strip offset value");
4421                 }
4422                 in.seek(skipBytes);
4423                 bytesRead += skipBytes;
4424 
4425                 // TODO: Need to handle potential OutOfMemoryError
4426                 // Read strip bytes
4427                 byte[] stripBytes = new byte[stripByteCount];
4428                 in.read(stripBytes);
4429                 bytesRead += stripByteCount;
4430 
4431                 // Add bytes to array
4432                 System.arraycopy(stripBytes, 0, totalStripBytes, bytesAdded,
4433                         stripBytes.length);
4434                 bytesAdded += stripBytes.length;
4435             }
4436             mThumbnailBytes = totalStripBytes;
4437 
4438             if (mAreThumbnailStripsConsecutive) {
4439                 // Need to add mExifOffset, which is the offset to the EXIF data segment
4440                 mThumbnailOffset = (int) stripOffsets[0] + mExifOffset;
4441                 mThumbnailLength = totalStripBytes.length;
4442             }
4443         }
4444     }
4445 
4446     // Check if thumbnail data type is currently supported or not
isSupportedDataType(HashMap thumbnailData)4447     private boolean isSupportedDataType(HashMap thumbnailData) throws IOException {
4448         ExifAttribute bitsPerSampleAttribute =
4449                 (ExifAttribute) thumbnailData.get(TAG_BITS_PER_SAMPLE);
4450         if (bitsPerSampleAttribute != null) {
4451             int[] bitsPerSampleValue = (int[]) bitsPerSampleAttribute.getValue(mExifByteOrder);
4452 
4453             if (Arrays.equals(BITS_PER_SAMPLE_RGB, bitsPerSampleValue)) {
4454                 return true;
4455             }
4456 
4457             // See DNG Specification 1.4.0.0. Section 3, Compression.
4458             if (mMimeType == IMAGE_TYPE_DNG) {
4459                 ExifAttribute photometricInterpretationAttribute =
4460                         (ExifAttribute) thumbnailData.get(TAG_PHOTOMETRIC_INTERPRETATION);
4461                 if (photometricInterpretationAttribute != null) {
4462                     int photometricInterpretationValue
4463                             = photometricInterpretationAttribute.getIntValue(mExifByteOrder);
4464                     if ((photometricInterpretationValue == PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO
4465                             && Arrays.equals(bitsPerSampleValue, BITS_PER_SAMPLE_GREYSCALE_2))
4466                             || ((photometricInterpretationValue == PHOTOMETRIC_INTERPRETATION_YCBCR)
4467                             && (Arrays.equals(bitsPerSampleValue, BITS_PER_SAMPLE_RGB)))) {
4468                         return true;
4469                     } else {
4470                         // TODO: Add support for lossless Huffman JPEG data
4471                     }
4472                 }
4473             }
4474         }
4475         if (DEBUG) {
4476             Log.d(TAG, "Unsupported data type value");
4477         }
4478         return false;
4479     }
4480 
4481     // Returns true if the image length and width values are <= 512.
4482     // See Section 4.8 of http://standardsproposals.bsigroup.com/Home/getPDF/567
isThumbnail(HashMap map)4483     private boolean isThumbnail(HashMap map) throws IOException {
4484         ExifAttribute imageLengthAttribute = (ExifAttribute) map.get(TAG_IMAGE_LENGTH);
4485         ExifAttribute imageWidthAttribute = (ExifAttribute) map.get(TAG_IMAGE_WIDTH);
4486 
4487         if (imageLengthAttribute != null && imageWidthAttribute != null) {
4488             int imageLengthValue = imageLengthAttribute.getIntValue(mExifByteOrder);
4489             int imageWidthValue = imageWidthAttribute.getIntValue(mExifByteOrder);
4490             if (imageLengthValue <= MAX_THUMBNAIL_SIZE && imageWidthValue <= MAX_THUMBNAIL_SIZE) {
4491                 return true;
4492             }
4493         }
4494         return false;
4495     }
4496 
4497     // Validate primary, preview, thumbnail image data by comparing image size
validateImages()4498     private void validateImages() throws IOException {
4499         // Swap images based on size (primary > preview > thumbnail)
4500         swapBasedOnImageSize(IFD_TYPE_PRIMARY, IFD_TYPE_PREVIEW);
4501         swapBasedOnImageSize(IFD_TYPE_PRIMARY, IFD_TYPE_THUMBNAIL);
4502         swapBasedOnImageSize(IFD_TYPE_PREVIEW, IFD_TYPE_THUMBNAIL);
4503 
4504         // TODO (b/142296453): Revise image width/height setting logic
4505         // Check if image has PixelXDimension/PixelYDimension tags, which contain valid image
4506         // sizes, excluding padding at the right end or bottom end of the image to make sure that
4507         // the values are multiples of 64. See JEITA CP-3451C Table 5 and Section 4.8.1. B.
4508         ExifAttribute pixelXDimAttribute =
4509                 (ExifAttribute) mAttributes[IFD_TYPE_EXIF].get(TAG_PIXEL_X_DIMENSION);
4510         ExifAttribute pixelYDimAttribute =
4511                 (ExifAttribute) mAttributes[IFD_TYPE_EXIF].get(TAG_PIXEL_Y_DIMENSION);
4512         if (pixelXDimAttribute != null && pixelYDimAttribute != null) {
4513             mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH, pixelXDimAttribute);
4514             mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH, pixelYDimAttribute);
4515         }
4516 
4517         // Check whether thumbnail image exists and whether preview image satisfies the thumbnail
4518         // image requirements
4519         if (mAttributes[IFD_TYPE_THUMBNAIL].isEmpty()) {
4520             if (isThumbnail(mAttributes[IFD_TYPE_PREVIEW])) {
4521                 mAttributes[IFD_TYPE_THUMBNAIL] = mAttributes[IFD_TYPE_PREVIEW];
4522                 mAttributes[IFD_TYPE_PREVIEW] = new HashMap();
4523             }
4524         }
4525 
4526         // Check if the thumbnail image satisfies the thumbnail size requirements
4527         if (!isThumbnail(mAttributes[IFD_TYPE_THUMBNAIL])) {
4528             Log.d(TAG, "No image meets the size requirements of a thumbnail image.");
4529         }
4530 
4531         // TAG_THUMBNAIL_* tags should be replaced with TAG_* equivalents and vice versa if needed.
4532         replaceInvalidTags(IFD_TYPE_PRIMARY, TAG_THUMBNAIL_ORIENTATION, TAG_ORIENTATION);
4533         replaceInvalidTags(IFD_TYPE_PRIMARY, TAG_THUMBNAIL_IMAGE_LENGTH, TAG_IMAGE_LENGTH);
4534         replaceInvalidTags(IFD_TYPE_PRIMARY, TAG_THUMBNAIL_IMAGE_WIDTH, TAG_IMAGE_WIDTH);
4535         replaceInvalidTags(IFD_TYPE_PREVIEW, TAG_THUMBNAIL_ORIENTATION, TAG_ORIENTATION);
4536         replaceInvalidTags(IFD_TYPE_PREVIEW, TAG_THUMBNAIL_IMAGE_LENGTH, TAG_IMAGE_LENGTH);
4537         replaceInvalidTags(IFD_TYPE_PREVIEW, TAG_THUMBNAIL_IMAGE_WIDTH, TAG_IMAGE_WIDTH);
4538         replaceInvalidTags(IFD_TYPE_THUMBNAIL, TAG_ORIENTATION, TAG_THUMBNAIL_ORIENTATION);
4539         replaceInvalidTags(IFD_TYPE_THUMBNAIL, TAG_IMAGE_LENGTH, TAG_THUMBNAIL_IMAGE_LENGTH);
4540         replaceInvalidTags(IFD_TYPE_THUMBNAIL, TAG_IMAGE_WIDTH, TAG_THUMBNAIL_IMAGE_WIDTH);
4541     }
4542 
4543     /**
4544      * If image is uncompressed, ImageWidth/Length tags are used to store size info.
4545      * However, uncompressed images often store extra pixels around the edges of the final image,
4546      * which results in larger values for TAG_IMAGE_WIDTH and TAG_IMAGE_LENGTH tags.
4547      * This method corrects those tag values by checking first the values of TAG_DEFAULT_CROP_SIZE
4548      * See DNG Specification 1.4.0.0. Section 4. (DefaultCropSize)
4549      *
4550      * If image is a RW2 file, valid image sizes are stored in SensorBorder tags.
4551      * See tiff_parser.cc GetFullDimension32()
4552      * */
updateImageSizeValues(ByteOrderedDataInputStream in, int imageType)4553     private void updateImageSizeValues(ByteOrderedDataInputStream in, int imageType)
4554             throws IOException {
4555         // Uncompressed image valid image size values
4556         ExifAttribute defaultCropSizeAttribute =
4557                 (ExifAttribute) mAttributes[imageType].get(TAG_DEFAULT_CROP_SIZE);
4558         // RW2 image valid image size values
4559         ExifAttribute topBorderAttribute =
4560                 (ExifAttribute) mAttributes[imageType].get(TAG_RW2_SENSOR_TOP_BORDER);
4561         ExifAttribute leftBorderAttribute =
4562                 (ExifAttribute) mAttributes[imageType].get(TAG_RW2_SENSOR_LEFT_BORDER);
4563         ExifAttribute bottomBorderAttribute =
4564                 (ExifAttribute) mAttributes[imageType].get(TAG_RW2_SENSOR_BOTTOM_BORDER);
4565         ExifAttribute rightBorderAttribute =
4566                 (ExifAttribute) mAttributes[imageType].get(TAG_RW2_SENSOR_RIGHT_BORDER);
4567 
4568         if (defaultCropSizeAttribute != null) {
4569             // Update for uncompressed image
4570             ExifAttribute defaultCropSizeXAttribute, defaultCropSizeYAttribute;
4571             if (defaultCropSizeAttribute.format == IFD_FORMAT_URATIONAL) {
4572                 Rational[] defaultCropSizeValue =
4573                         (Rational[]) defaultCropSizeAttribute.getValue(mExifByteOrder);
4574                 defaultCropSizeXAttribute =
4575                         ExifAttribute.createURational(defaultCropSizeValue[0], mExifByteOrder);
4576                 defaultCropSizeYAttribute =
4577                         ExifAttribute.createURational(defaultCropSizeValue[1], mExifByteOrder);
4578             } else {
4579                 int[] defaultCropSizeValue =
4580                         (int[]) defaultCropSizeAttribute.getValue(mExifByteOrder);
4581                 defaultCropSizeXAttribute =
4582                         ExifAttribute.createUShort(defaultCropSizeValue[0], mExifByteOrder);
4583                 defaultCropSizeYAttribute =
4584                         ExifAttribute.createUShort(defaultCropSizeValue[1], mExifByteOrder);
4585             }
4586             mAttributes[imageType].put(TAG_IMAGE_WIDTH, defaultCropSizeXAttribute);
4587             mAttributes[imageType].put(TAG_IMAGE_LENGTH, defaultCropSizeYAttribute);
4588         } else if (topBorderAttribute != null && leftBorderAttribute != null &&
4589                 bottomBorderAttribute != null && rightBorderAttribute != null) {
4590             // Update for RW2 image
4591             int topBorderValue = topBorderAttribute.getIntValue(mExifByteOrder);
4592             int bottomBorderValue = bottomBorderAttribute.getIntValue(mExifByteOrder);
4593             int rightBorderValue = rightBorderAttribute.getIntValue(mExifByteOrder);
4594             int leftBorderValue = leftBorderAttribute.getIntValue(mExifByteOrder);
4595             if (bottomBorderValue > topBorderValue && rightBorderValue > leftBorderValue) {
4596                 int length = bottomBorderValue - topBorderValue;
4597                 int width = rightBorderValue - leftBorderValue;
4598                 ExifAttribute imageLengthAttribute =
4599                         ExifAttribute.createUShort(length, mExifByteOrder);
4600                 ExifAttribute imageWidthAttribute =
4601                         ExifAttribute.createUShort(width, mExifByteOrder);
4602                 mAttributes[imageType].put(TAG_IMAGE_LENGTH, imageLengthAttribute);
4603                 mAttributes[imageType].put(TAG_IMAGE_WIDTH, imageWidthAttribute);
4604             }
4605         } else {
4606             retrieveJpegImageSize(in, imageType);
4607         }
4608     }
4609 
4610     // Writes an Exif segment into the given output stream.
writeExifSegment(ByteOrderedDataOutputStream dataOutputStream)4611     private int writeExifSegment(ByteOrderedDataOutputStream dataOutputStream) throws IOException {
4612         // The following variables are for calculating each IFD tag group size in bytes.
4613         int[] ifdOffsets = new int[EXIF_TAGS.length];
4614         int[] ifdDataSizes = new int[EXIF_TAGS.length];
4615 
4616         // Remove IFD pointer tags (we'll re-add it later.)
4617         for (ExifTag tag : EXIF_POINTER_TAGS) {
4618             removeAttribute(tag.name);
4619         }
4620         // Remove old thumbnail data
4621         if (mHasThumbnail) {
4622             if (mHasThumbnailStrips) {
4623                 removeAttribute(TAG_STRIP_OFFSETS);
4624                 removeAttribute(TAG_STRIP_BYTE_COUNTS);
4625             } else {
4626                 removeAttribute(TAG_JPEG_INTERCHANGE_FORMAT);
4627                 removeAttribute(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
4628             }
4629         }
4630 
4631         // Remove null value tags.
4632         for (int ifdType = 0; ifdType < EXIF_TAGS.length; ++ifdType) {
4633             for (Object obj : mAttributes[ifdType].entrySet().toArray()) {
4634                 final Map.Entry entry = (Map.Entry) obj;
4635                 if (entry.getValue() == null) {
4636                     mAttributes[ifdType].remove(entry.getKey());
4637                 }
4638             }
4639         }
4640 
4641         // Add IFD pointer tags. The next offset of primary image TIFF IFD will have thumbnail IFD
4642         // offset when there is one or more tags in the thumbnail IFD.
4643         if (!mAttributes[IFD_TYPE_EXIF].isEmpty()) {
4644             mAttributes[IFD_TYPE_PRIMARY].put(EXIF_POINTER_TAGS[1].name,
4645                     ExifAttribute.createULong(0, mExifByteOrder));
4646         }
4647         if (!mAttributes[IFD_TYPE_GPS].isEmpty()) {
4648             mAttributes[IFD_TYPE_PRIMARY].put(EXIF_POINTER_TAGS[2].name,
4649                     ExifAttribute.createULong(0, mExifByteOrder));
4650         }
4651         if (!mAttributes[IFD_TYPE_INTEROPERABILITY].isEmpty()) {
4652             mAttributes[IFD_TYPE_EXIF].put(EXIF_POINTER_TAGS[3].name,
4653                     ExifAttribute.createULong(0, mExifByteOrder));
4654         }
4655         if (mHasThumbnail) {
4656             if (mHasThumbnailStrips) {
4657                 mAttributes[IFD_TYPE_THUMBNAIL].put(TAG_STRIP_OFFSETS,
4658                         ExifAttribute.createUShort(0, mExifByteOrder));
4659                 mAttributes[IFD_TYPE_THUMBNAIL].put(TAG_STRIP_BYTE_COUNTS,
4660                         ExifAttribute.createUShort(mThumbnailLength, mExifByteOrder));
4661             } else {
4662                 mAttributes[IFD_TYPE_THUMBNAIL].put(TAG_JPEG_INTERCHANGE_FORMAT,
4663                         ExifAttribute.createULong(0, mExifByteOrder));
4664                 mAttributes[IFD_TYPE_THUMBNAIL].put(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
4665                         ExifAttribute.createULong(mThumbnailLength, mExifByteOrder));
4666             }
4667         }
4668 
4669         // Calculate IFD group data area sizes. IFD group data area is assigned to save the entry
4670         // value which has a bigger size than 4 bytes.
4671         for (int i = 0; i < EXIF_TAGS.length; ++i) {
4672             int sum = 0;
4673             for (Map.Entry entry : (Set<Map.Entry>) mAttributes[i].entrySet()) {
4674                 final ExifAttribute exifAttribute = (ExifAttribute) entry.getValue();
4675                 final int size = exifAttribute.size();
4676                 if (size > 4) {
4677                     sum += size;
4678                 }
4679             }
4680             ifdDataSizes[i] += sum;
4681         }
4682 
4683         // Calculate IFD offsets.
4684         // 8 bytes are for TIFF headers: 2 bytes (byte order) + 2 bytes (identifier) + 4 bytes
4685         // (offset of IFDs)
4686         int position = 8;
4687         for (int ifdType = 0; ifdType < EXIF_TAGS.length; ++ifdType) {
4688             if (!mAttributes[ifdType].isEmpty()) {
4689                 ifdOffsets[ifdType] = position;
4690                 position += 2 + mAttributes[ifdType].size() * 12 + 4 + ifdDataSizes[ifdType];
4691             }
4692         }
4693         if (mHasThumbnail) {
4694             int thumbnailOffset = position;
4695             if (mHasThumbnailStrips) {
4696                 mAttributes[IFD_TYPE_THUMBNAIL].put(TAG_STRIP_OFFSETS,
4697                         ExifAttribute.createUShort(thumbnailOffset, mExifByteOrder));
4698             } else {
4699                 mAttributes[IFD_TYPE_THUMBNAIL].put(TAG_JPEG_INTERCHANGE_FORMAT,
4700                         ExifAttribute.createULong(thumbnailOffset, mExifByteOrder));
4701             }
4702             // Need to add mExifOffset, which is the offset to the EXIF data segment
4703             mThumbnailOffset = thumbnailOffset + mExifOffset;
4704             position += mThumbnailLength;
4705         }
4706 
4707         int totalSize = position;
4708         if (mMimeType == IMAGE_TYPE_JPEG) {
4709             // Add 8 bytes for APP1 size and identifier data
4710             totalSize += 8;
4711         }
4712         if (DEBUG) {
4713             for (int i = 0; i < EXIF_TAGS.length; ++i) {
4714                 Log.d(TAG, String.format("index: %d, offsets: %d, tag count: %d, data sizes: %d, "
4715                                 + "total size: %d", i, ifdOffsets[i], mAttributes[i].size(),
4716                         ifdDataSizes[i], totalSize));
4717             }
4718         }
4719 
4720         // Update IFD pointer tags with the calculated offsets.
4721         if (!mAttributes[IFD_TYPE_EXIF].isEmpty()) {
4722             mAttributes[IFD_TYPE_PRIMARY].put(EXIF_POINTER_TAGS[1].name,
4723                     ExifAttribute.createULong(ifdOffsets[IFD_TYPE_EXIF], mExifByteOrder));
4724         }
4725         if (!mAttributes[IFD_TYPE_GPS].isEmpty()) {
4726             mAttributes[IFD_TYPE_PRIMARY].put(EXIF_POINTER_TAGS[2].name,
4727                     ExifAttribute.createULong(ifdOffsets[IFD_TYPE_GPS], mExifByteOrder));
4728         }
4729         if (!mAttributes[IFD_TYPE_INTEROPERABILITY].isEmpty()) {
4730             mAttributes[IFD_TYPE_EXIF].put(EXIF_POINTER_TAGS[3].name, ExifAttribute.createULong(
4731                     ifdOffsets[IFD_TYPE_INTEROPERABILITY], mExifByteOrder));
4732         }
4733 
4734         switch (mMimeType) {
4735             case IMAGE_TYPE_JPEG:
4736                 // Write JPEG specific data (APP1 size, APP1 identifier)
4737                 dataOutputStream.writeUnsignedShort(totalSize);
4738                 dataOutputStream.write(IDENTIFIER_EXIF_APP1);
4739                 break;
4740             case IMAGE_TYPE_PNG:
4741                 // Write PNG specific data (chunk size, chunk type)
4742                 dataOutputStream.writeInt(totalSize);
4743                 dataOutputStream.write(PNG_CHUNK_TYPE_EXIF);
4744                 break;
4745             case IMAGE_TYPE_WEBP:
4746                 // Write WebP specific data (chunk type, chunk size)
4747                 dataOutputStream.write(WEBP_CHUNK_TYPE_EXIF);
4748                 dataOutputStream.writeInt(totalSize);
4749                 break;
4750         }
4751 
4752         // Write TIFF Headers. See JEITA CP-3451C Section 4.5.2. Table 1.
4753         dataOutputStream.writeShort(mExifByteOrder == ByteOrder.BIG_ENDIAN
4754                 ? BYTE_ALIGN_MM : BYTE_ALIGN_II);
4755         dataOutputStream.setByteOrder(mExifByteOrder);
4756         dataOutputStream.writeUnsignedShort(START_CODE);
4757         dataOutputStream.writeUnsignedInt(IFD_OFFSET);
4758 
4759         // Write IFD groups. See JEITA CP-3451C Section 4.5.8. Figure 9.
4760         for (int ifdType = 0; ifdType < EXIF_TAGS.length; ++ifdType) {
4761             if (!mAttributes[ifdType].isEmpty()) {
4762                 // See JEITA CP-3451C Section 4.6.2: IFD structure.
4763                 // Write entry count
4764                 dataOutputStream.writeUnsignedShort(mAttributes[ifdType].size());
4765 
4766                 // Write entry info
4767                 int dataOffset = ifdOffsets[ifdType] + 2 + mAttributes[ifdType].size() * 12 + 4;
4768                 for (Map.Entry entry : (Set<Map.Entry>) mAttributes[ifdType].entrySet()) {
4769                     // Convert tag name to tag number.
4770                     final ExifTag tag =
4771                             (ExifTag) sExifTagMapsForWriting[ifdType].get(entry.getKey());
4772                     final int tagNumber = tag.number;
4773                     final ExifAttribute attribute = (ExifAttribute) entry.getValue();
4774                     final int size = attribute.size();
4775 
4776                     dataOutputStream.writeUnsignedShort(tagNumber);
4777                     dataOutputStream.writeUnsignedShort(attribute.format);
4778                     dataOutputStream.writeInt(attribute.numberOfComponents);
4779                     if (size > 4) {
4780                         dataOutputStream.writeUnsignedInt(dataOffset);
4781                         dataOffset += size;
4782                     } else {
4783                         dataOutputStream.write(attribute.bytes);
4784                         // Fill zero up to 4 bytes
4785                         if (size < 4) {
4786                             for (int i = size; i < 4; ++i) {
4787                                 dataOutputStream.writeByte(0);
4788                             }
4789                         }
4790                     }
4791                 }
4792 
4793                 // Write the next offset. It writes the offset of thumbnail IFD if there is one or
4794                 // more tags in the thumbnail IFD when the current IFD is the primary image TIFF
4795                 // IFD; Otherwise 0.
4796                 if (ifdType == 0 && !mAttributes[IFD_TYPE_THUMBNAIL].isEmpty()) {
4797                     dataOutputStream.writeUnsignedInt(ifdOffsets[IFD_TYPE_THUMBNAIL]);
4798                 } else {
4799                     dataOutputStream.writeUnsignedInt(0);
4800                 }
4801 
4802                 // Write values of data field exceeding 4 bytes after the next offset.
4803                 for (Map.Entry entry : (Set<Map.Entry>) mAttributes[ifdType].entrySet()) {
4804                     ExifAttribute attribute = (ExifAttribute) entry.getValue();
4805 
4806                     if (attribute.bytes.length > 4) {
4807                         dataOutputStream.write(attribute.bytes, 0, attribute.bytes.length);
4808                     }
4809                 }
4810             }
4811         }
4812 
4813         // Write thumbnail
4814         if (mHasThumbnail) {
4815             dataOutputStream.write(getThumbnailBytes());
4816         }
4817 
4818         // For WebP files, add a single padding byte at end if chunk size is odd
4819         if (mMimeType == IMAGE_TYPE_WEBP && totalSize % 2 == 1) {
4820             dataOutputStream.writeByte(0);
4821         }
4822 
4823         // Reset the byte order to big endian in order to write remaining parts of the JPEG file.
4824         dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN);
4825 
4826         return totalSize;
4827     }
4828 
4829     /**
4830      * Determines the data format of EXIF entry value.
4831      *
4832      * @param entryValue The value to be determined.
4833      * @return Returns two data formats guessed as a pair in integer. If there is no two candidate
4834                data formats for the given entry value, returns {@code -1} in the second of the pair.
4835      */
guessDataFormat(String entryValue)4836     private static Pair<Integer, Integer> guessDataFormat(String entryValue) {
4837         // See TIFF 6.0 Section 2, "Image File Directory".
4838         // Take the first component if there are more than one component.
4839         if (entryValue.contains(",")) {
4840             String[] entryValues = entryValue.split(",");
4841             Pair<Integer, Integer> dataFormat = guessDataFormat(entryValues[0]);
4842             if (dataFormat.first == IFD_FORMAT_STRING) {
4843                 return dataFormat;
4844             }
4845             for (int i = 1; i < entryValues.length; ++i) {
4846                 final Pair<Integer, Integer> guessDataFormat = guessDataFormat(entryValues[i]);
4847                 int first = -1, second = -1;
4848                 if (Objects.equals(guessDataFormat.first, dataFormat.first)
4849                         || Objects.equals(guessDataFormat.second, dataFormat.first)) {
4850                     first = dataFormat.first;
4851                 }
4852                 if (dataFormat.second != -1
4853                         && (Objects.equals(guessDataFormat.first, dataFormat.second)
4854                         || Objects.equals(guessDataFormat.second, dataFormat.second))) {
4855                     second = dataFormat.second;
4856                 }
4857                 if (first == -1 && second == -1) {
4858                     return new Pair<>(IFD_FORMAT_STRING, -1);
4859                 }
4860                 if (first == -1) {
4861                     dataFormat = new Pair<>(second, -1);
4862                     continue;
4863                 }
4864                 if (second == -1) {
4865                     dataFormat = new Pair<>(first, -1);
4866                     continue;
4867                 }
4868             }
4869             return dataFormat;
4870         }
4871 
4872         if (entryValue.contains("/")) {
4873             String[] rationalNumber = entryValue.split("/");
4874             if (rationalNumber.length == 2) {
4875                 try {
4876                     long numerator = (long) Double.parseDouble(rationalNumber[0]);
4877                     long denominator = (long) Double.parseDouble(rationalNumber[1]);
4878                     if (numerator < 0L || denominator < 0L) {
4879                         return new Pair<>(IFD_FORMAT_SRATIONAL, -1);
4880                     }
4881                     if (numerator > Integer.MAX_VALUE || denominator > Integer.MAX_VALUE) {
4882                         return new Pair<>(IFD_FORMAT_URATIONAL, -1);
4883                     }
4884                     return new Pair<>(IFD_FORMAT_SRATIONAL, IFD_FORMAT_URATIONAL);
4885                 } catch (NumberFormatException e)  {
4886                     // Ignored
4887                 }
4888             }
4889             return new Pair<>(IFD_FORMAT_STRING, -1);
4890         }
4891         try {
4892             Long longValue = Long.parseLong(entryValue);
4893             if (longValue >= 0 && longValue <= 65535) {
4894                 return new Pair<>(IFD_FORMAT_USHORT, IFD_FORMAT_ULONG);
4895             }
4896             if (longValue < 0) {
4897                 return new Pair<>(IFD_FORMAT_SLONG, -1);
4898             }
4899             return new Pair<>(IFD_FORMAT_ULONG, -1);
4900         } catch (NumberFormatException e) {
4901             // Ignored
4902         }
4903         try {
4904             Double.parseDouble(entryValue);
4905             return new Pair<>(IFD_FORMAT_DOUBLE, -1);
4906         } catch (NumberFormatException e) {
4907             // Ignored
4908         }
4909         return new Pair<>(IFD_FORMAT_STRING, -1);
4910     }
4911 
4912     // An input stream to parse EXIF data area, which can be written in either little or big endian
4913     // order.
4914     private static class ByteOrderedDataInputStream extends InputStream implements DataInput {
4915         private static final ByteOrder LITTLE_ENDIAN = ByteOrder.LITTLE_ENDIAN;
4916         private static final ByteOrder BIG_ENDIAN = ByteOrder.BIG_ENDIAN;
4917 
4918         private DataInputStream mDataInputStream;
4919         private InputStream mInputStream;
4920         private ByteOrder mByteOrder = ByteOrder.BIG_ENDIAN;
4921         private final int mLength;
4922         private int mPosition;
4923 
ByteOrderedDataInputStream(InputStream in)4924         public ByteOrderedDataInputStream(InputStream in) throws IOException {
4925             this(in, ByteOrder.BIG_ENDIAN);
4926         }
4927 
ByteOrderedDataInputStream(InputStream in, ByteOrder byteOrder)4928         ByteOrderedDataInputStream(InputStream in, ByteOrder byteOrder) throws IOException {
4929             mInputStream = in;
4930             mDataInputStream = new DataInputStream(in);
4931             mLength = mDataInputStream.available();
4932             mPosition = 0;
4933             // TODO (b/142218289): Need to handle case where input stream does not support mark
4934             mDataInputStream.mark(mLength);
4935             mByteOrder = byteOrder;
4936         }
4937 
ByteOrderedDataInputStream(byte[] bytes)4938         public ByteOrderedDataInputStream(byte[] bytes) throws IOException {
4939             this(new ByteArrayInputStream(bytes));
4940         }
4941 
setByteOrder(ByteOrder byteOrder)4942         public void setByteOrder(ByteOrder byteOrder) {
4943             mByteOrder = byteOrder;
4944         }
4945 
seek(long byteCount)4946         public void seek(long byteCount) throws IOException {
4947             if (mPosition > byteCount) {
4948                 mPosition = 0;
4949                 mDataInputStream.reset();
4950                 // TODO (b/142218289): Need to handle case where input stream does not support mark
4951                 mDataInputStream.mark(mLength);
4952             } else {
4953                 byteCount -= mPosition;
4954             }
4955 
4956             if (skipBytes((int) byteCount) != (int) byteCount) {
4957                 throw new IOException("Couldn't seek up to the byteCount");
4958             }
4959         }
4960 
peek()4961         public int peek() {
4962             return mPosition;
4963         }
4964 
4965         @Override
available()4966         public int available() throws IOException {
4967             return mDataInputStream.available();
4968         }
4969 
4970         @Override
read()4971         public int read() throws IOException {
4972             ++mPosition;
4973             return mDataInputStream.read();
4974         }
4975 
4976         @Override
readUnsignedByte()4977         public int readUnsignedByte() throws IOException {
4978             ++mPosition;
4979             return mDataInputStream.readUnsignedByte();
4980         }
4981 
4982         @Override
readLine()4983         public String readLine() throws IOException {
4984             Log.d(TAG, "Currently unsupported");
4985             return null;
4986         }
4987 
4988         @Override
readBoolean()4989         public boolean readBoolean() throws IOException {
4990             ++mPosition;
4991             return mDataInputStream.readBoolean();
4992         }
4993 
4994         @Override
readChar()4995         public char readChar() throws IOException {
4996             mPosition += 2;
4997             return mDataInputStream.readChar();
4998         }
4999 
5000         @Override
readUTF()5001         public String readUTF() throws IOException {
5002             mPosition += 2;
5003             return mDataInputStream.readUTF();
5004         }
5005 
5006         @Override
readFully(byte[] buffer, int offset, int length)5007         public void readFully(byte[] buffer, int offset, int length) throws IOException {
5008             mPosition += length;
5009             if (mPosition > mLength) {
5010                 throw new EOFException();
5011             }
5012             if (mDataInputStream.read(buffer, offset, length) != length) {
5013                 throw new IOException("Couldn't read up to the length of buffer");
5014             }
5015         }
5016 
5017         @Override
readFully(byte[] buffer)5018         public void readFully(byte[] buffer) throws IOException {
5019             mPosition += buffer.length;
5020             if (mPosition > mLength) {
5021                 throw new EOFException();
5022             }
5023             if (mDataInputStream.read(buffer, 0, buffer.length) != buffer.length) {
5024                 throw new IOException("Couldn't read up to the length of buffer");
5025             }
5026         }
5027 
5028         @Override
readByte()5029         public byte readByte() throws IOException {
5030             ++mPosition;
5031             if (mPosition > mLength) {
5032                 throw new EOFException();
5033             }
5034             int ch = mDataInputStream.read();
5035             if (ch < 0) {
5036                 throw new EOFException();
5037             }
5038             return (byte) ch;
5039         }
5040 
5041         @Override
readShort()5042         public short readShort() throws IOException {
5043             mPosition += 2;
5044             if (mPosition > mLength) {
5045                 throw new EOFException();
5046             }
5047             int ch1 = mDataInputStream.read();
5048             int ch2 = mDataInputStream.read();
5049             if ((ch1 | ch2) < 0) {
5050                 throw new EOFException();
5051             }
5052             if (mByteOrder == LITTLE_ENDIAN) {
5053                 return (short) ((ch2 << 8) + (ch1));
5054             } else if (mByteOrder == BIG_ENDIAN) {
5055                 return (short) ((ch1 << 8) + (ch2));
5056             }
5057             throw new IOException("Invalid byte order: " + mByteOrder);
5058         }
5059 
5060         @Override
readInt()5061         public int readInt() throws IOException {
5062             mPosition += 4;
5063             if (mPosition > mLength) {
5064                 throw new EOFException();
5065             }
5066             int ch1 = mDataInputStream.read();
5067             int ch2 = mDataInputStream.read();
5068             int ch3 = mDataInputStream.read();
5069             int ch4 = mDataInputStream.read();
5070             if ((ch1 | ch2 | ch3 | ch4) < 0) {
5071                 throw new EOFException();
5072             }
5073             if (mByteOrder == LITTLE_ENDIAN) {
5074                 return ((ch4 << 24) + (ch3 << 16) + (ch2 << 8) + ch1);
5075             } else if (mByteOrder == BIG_ENDIAN) {
5076                 return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4);
5077             }
5078             throw new IOException("Invalid byte order: " + mByteOrder);
5079         }
5080 
5081         @Override
skipBytes(int byteCount)5082         public int skipBytes(int byteCount) throws IOException {
5083             int totalBytesToSkip = Math.min(byteCount, mLength - mPosition);
5084             int totalSkipped = 0;
5085             while (totalSkipped < totalBytesToSkip) {
5086                 int skipped = mDataInputStream.skipBytes(totalBytesToSkip - totalSkipped);
5087                 if (skipped > 0) {
5088                     totalSkipped += skipped;
5089                 } else {
5090                     break;
5091                 }
5092             }
5093             mPosition += totalSkipped;
5094             return totalSkipped;
5095         }
5096 
readUnsignedShort()5097         public int readUnsignedShort() throws IOException {
5098             mPosition += 2;
5099             if (mPosition > mLength) {
5100                 throw new EOFException();
5101             }
5102             int ch1 = mDataInputStream.read();
5103             int ch2 = mDataInputStream.read();
5104             if ((ch1 | ch2) < 0) {
5105                 throw new EOFException();
5106             }
5107             if (mByteOrder == LITTLE_ENDIAN) {
5108                 return ((ch2 << 8) + (ch1));
5109             } else if (mByteOrder == BIG_ENDIAN) {
5110                 return ((ch1 << 8) + (ch2));
5111             }
5112             throw new IOException("Invalid byte order: " + mByteOrder);
5113         }
5114 
readUnsignedInt()5115         public long readUnsignedInt() throws IOException {
5116             return readInt() & 0xffffffffL;
5117         }
5118 
5119         @Override
readLong()5120         public long readLong() throws IOException {
5121             mPosition += 8;
5122             if (mPosition > mLength) {
5123                 throw new EOFException();
5124             }
5125             int ch1 = mDataInputStream.read();
5126             int ch2 = mDataInputStream.read();
5127             int ch3 = mDataInputStream.read();
5128             int ch4 = mDataInputStream.read();
5129             int ch5 = mDataInputStream.read();
5130             int ch6 = mDataInputStream.read();
5131             int ch7 = mDataInputStream.read();
5132             int ch8 = mDataInputStream.read();
5133             if ((ch1 | ch2 | ch3 | ch4 | ch5 | ch6 | ch7 | ch8) < 0) {
5134                 throw new EOFException();
5135             }
5136             if (mByteOrder == LITTLE_ENDIAN) {
5137                 return (((long) ch8 << 56) + ((long) ch7 << 48) + ((long) ch6 << 40)
5138                         + ((long) ch5 << 32) + ((long) ch4 << 24) + ((long) ch3 << 16)
5139                         + ((long) ch2 << 8) + (long) ch1);
5140             } else if (mByteOrder == BIG_ENDIAN) {
5141                 return (((long) ch1 << 56) + ((long) ch2 << 48) + ((long) ch3 << 40)
5142                         + ((long) ch4 << 32) + ((long) ch5 << 24) + ((long) ch6 << 16)
5143                         + ((long) ch7 << 8) + (long) ch8);
5144             }
5145             throw new IOException("Invalid byte order: " + mByteOrder);
5146         }
5147 
5148         @Override
readFloat()5149         public float readFloat() throws IOException {
5150             return Float.intBitsToFloat(readInt());
5151         }
5152 
5153         @Override
readDouble()5154         public double readDouble() throws IOException {
5155             return Double.longBitsToDouble(readLong());
5156         }
5157 
getLength()5158         public int getLength() {
5159             return mLength;
5160         }
5161     }
5162 
5163     // An output stream to write EXIF data area, which can be written in either little or big endian
5164     // order.
5165     private static class ByteOrderedDataOutputStream extends FilterOutputStream {
5166         final OutputStream mOutputStream;
5167         private ByteOrder mByteOrder;
5168 
ByteOrderedDataOutputStream(OutputStream out, ByteOrder byteOrder)5169         public ByteOrderedDataOutputStream(OutputStream out, ByteOrder byteOrder) {
5170             super(out);
5171             mOutputStream = out;
5172             mByteOrder = byteOrder;
5173         }
5174 
setByteOrder(ByteOrder byteOrder)5175         public void setByteOrder(ByteOrder byteOrder) {
5176             mByteOrder = byteOrder;
5177         }
5178 
write(byte[] bytes)5179         public void write(byte[] bytes) throws IOException {
5180             mOutputStream.write(bytes);
5181         }
5182 
write(byte[] bytes, int offset, int length)5183         public void write(byte[] bytes, int offset, int length) throws IOException {
5184             mOutputStream.write(bytes, offset, length);
5185         }
5186 
writeByte(int val)5187         public void writeByte(int val) throws IOException {
5188             mOutputStream.write(val);
5189         }
5190 
writeShort(short val)5191         public void writeShort(short val) throws IOException {
5192             if (mByteOrder == ByteOrder.LITTLE_ENDIAN) {
5193                 mOutputStream.write((val >>> 0) & 0xFF);
5194                 mOutputStream.write((val >>> 8) & 0xFF);
5195             } else if (mByteOrder == ByteOrder.BIG_ENDIAN) {
5196                 mOutputStream.write((val >>> 8) & 0xFF);
5197                 mOutputStream.write((val >>> 0) & 0xFF);
5198             }
5199         }
5200 
writeInt(int val)5201         public void writeInt(int val) throws IOException {
5202             if (mByteOrder == ByteOrder.LITTLE_ENDIAN) {
5203                 mOutputStream.write((val >>> 0) & 0xFF);
5204                 mOutputStream.write((val >>> 8) & 0xFF);
5205                 mOutputStream.write((val >>> 16) & 0xFF);
5206                 mOutputStream.write((val >>> 24) & 0xFF);
5207             } else if (mByteOrder == ByteOrder.BIG_ENDIAN) {
5208                 mOutputStream.write((val >>> 24) & 0xFF);
5209                 mOutputStream.write((val >>> 16) & 0xFF);
5210                 mOutputStream.write((val >>> 8) & 0xFF);
5211                 mOutputStream.write((val >>> 0) & 0xFF);
5212             }
5213         }
5214 
writeUnsignedShort(int val)5215         public void writeUnsignedShort(int val) throws IOException {
5216             writeShort((short) val);
5217         }
5218 
writeUnsignedInt(long val)5219         public void writeUnsignedInt(long val) throws IOException {
5220             writeInt((int) val);
5221         }
5222     }
5223 
5224     // Swaps image data based on image size
swapBasedOnImageSize(@fdType int firstIfdType, @IfdType int secondIfdType)5225     private void swapBasedOnImageSize(@IfdType int firstIfdType, @IfdType int secondIfdType)
5226             throws IOException {
5227         if (mAttributes[firstIfdType].isEmpty() || mAttributes[secondIfdType].isEmpty()) {
5228             if (DEBUG) {
5229                 Log.d(TAG, "Cannot perform swap since only one image data exists");
5230             }
5231             return;
5232         }
5233 
5234         ExifAttribute firstImageLengthAttribute =
5235                 (ExifAttribute) mAttributes[firstIfdType].get(TAG_IMAGE_LENGTH);
5236         ExifAttribute firstImageWidthAttribute =
5237                 (ExifAttribute) mAttributes[firstIfdType].get(TAG_IMAGE_WIDTH);
5238         ExifAttribute secondImageLengthAttribute =
5239                 (ExifAttribute) mAttributes[secondIfdType].get(TAG_IMAGE_LENGTH);
5240         ExifAttribute secondImageWidthAttribute =
5241                 (ExifAttribute) mAttributes[secondIfdType].get(TAG_IMAGE_WIDTH);
5242 
5243         if (firstImageLengthAttribute == null || firstImageWidthAttribute == null) {
5244             if (DEBUG) {
5245                 Log.d(TAG, "First image does not contain valid size information");
5246             }
5247         } else if (secondImageLengthAttribute == null || secondImageWidthAttribute == null) {
5248             if (DEBUG) {
5249                 Log.d(TAG, "Second image does not contain valid size information");
5250             }
5251         } else {
5252             int firstImageLengthValue = firstImageLengthAttribute.getIntValue(mExifByteOrder);
5253             int firstImageWidthValue = firstImageWidthAttribute.getIntValue(mExifByteOrder);
5254             int secondImageLengthValue = secondImageLengthAttribute.getIntValue(mExifByteOrder);
5255             int secondImageWidthValue = secondImageWidthAttribute.getIntValue(mExifByteOrder);
5256 
5257             if (firstImageLengthValue < secondImageLengthValue &&
5258                     firstImageWidthValue < secondImageWidthValue) {
5259                 HashMap tempMap = mAttributes[firstIfdType];
5260                 mAttributes[firstIfdType] = mAttributes[secondIfdType];
5261                 mAttributes[secondIfdType] = tempMap;
5262             }
5263         }
5264     }
5265 
replaceInvalidTags(@fdType int ifdType, String invalidTag, String validTag)5266     private void replaceInvalidTags(@IfdType int ifdType, String invalidTag, String validTag) {
5267         if (!mAttributes[ifdType].isEmpty()) {
5268             if (mAttributes[ifdType].get(invalidTag) != null) {
5269                 mAttributes[ifdType].put(validTag,
5270                         mAttributes[ifdType].get(invalidTag));
5271                 mAttributes[ifdType].remove(invalidTag);
5272             }
5273         }
5274     }
5275 
isSupportedFormatForSavingAttributes()5276     private boolean isSupportedFormatForSavingAttributes() {
5277         if (mIsSupportedFile && (mMimeType == IMAGE_TYPE_JPEG || mMimeType == IMAGE_TYPE_PNG
5278                 || mMimeType == IMAGE_TYPE_WEBP)) {
5279             return true;
5280         }
5281         return false;
5282     }
5283 }
5284