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