1 /*
2  * Copyright 2020 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 androidx.camera.core.impl.utils;
18 
19 import static androidx.camera.core.impl.utils.ExifAttribute.IFD_FORMAT_BYTE;
20 import static androidx.camera.core.impl.utils.ExifAttribute.IFD_FORMAT_DOUBLE;
21 import static androidx.camera.core.impl.utils.ExifAttribute.IFD_FORMAT_SLONG;
22 import static androidx.camera.core.impl.utils.ExifAttribute.IFD_FORMAT_SRATIONAL;
23 import static androidx.camera.core.impl.utils.ExifAttribute.IFD_FORMAT_STRING;
24 import static androidx.camera.core.impl.utils.ExifAttribute.IFD_FORMAT_ULONG;
25 import static androidx.camera.core.impl.utils.ExifAttribute.IFD_FORMAT_UNDEFINED;
26 import static androidx.camera.core.impl.utils.ExifAttribute.IFD_FORMAT_URATIONAL;
27 import static androidx.camera.core.impl.utils.ExifAttribute.IFD_FORMAT_USHORT;
28 import static androidx.exifinterface.media.ExifInterface.CONTRAST_NORMAL;
29 import static androidx.exifinterface.media.ExifInterface.EXPOSURE_PROGRAM_NOT_DEFINED;
30 import static androidx.exifinterface.media.ExifInterface.FILE_SOURCE_DSC;
31 import static androidx.exifinterface.media.ExifInterface.FLAG_FLASH_FIRED;
32 import static androidx.exifinterface.media.ExifInterface.FLAG_FLASH_NO_FLASH_FUNCTION;
33 import static androidx.exifinterface.media.ExifInterface.GPS_DIRECTION_TRUE;
34 import static androidx.exifinterface.media.ExifInterface.GPS_DISTANCE_KILOMETERS;
35 import static androidx.exifinterface.media.ExifInterface.GPS_SPEED_KILOMETERS_PER_HOUR;
36 import static androidx.exifinterface.media.ExifInterface.LIGHT_SOURCE_FLASH;
37 import static androidx.exifinterface.media.ExifInterface.LIGHT_SOURCE_UNKNOWN;
38 import static androidx.exifinterface.media.ExifInterface.METERING_MODE_UNKNOWN;
39 import static androidx.exifinterface.media.ExifInterface.ORIENTATION_NORMAL;
40 import static androidx.exifinterface.media.ExifInterface.RENDERED_PROCESS_NORMAL;
41 import static androidx.exifinterface.media.ExifInterface.RESOLUTION_UNIT_INCHES;
42 import static androidx.exifinterface.media.ExifInterface.SATURATION_NORMAL;
43 import static androidx.exifinterface.media.ExifInterface.SCENE_CAPTURE_TYPE_STANDARD;
44 import static androidx.exifinterface.media.ExifInterface.SCENE_TYPE_DIRECTLY_PHOTOGRAPHED;
45 import static androidx.exifinterface.media.ExifInterface.SENSITIVITY_TYPE_ISO_SPEED;
46 import static androidx.exifinterface.media.ExifInterface.SHARPNESS_NORMAL;
47 import static androidx.exifinterface.media.ExifInterface.TAG_APERTURE_VALUE;
48 import static androidx.exifinterface.media.ExifInterface.TAG_BRIGHTNESS_VALUE;
49 import static androidx.exifinterface.media.ExifInterface.TAG_COLOR_SPACE;
50 import static androidx.exifinterface.media.ExifInterface.TAG_COMPONENTS_CONFIGURATION;
51 import static androidx.exifinterface.media.ExifInterface.TAG_CONTRAST;
52 import static androidx.exifinterface.media.ExifInterface.TAG_CUSTOM_RENDERED;
53 import static androidx.exifinterface.media.ExifInterface.TAG_DATETIME;
54 import static androidx.exifinterface.media.ExifInterface.TAG_DATETIME_DIGITIZED;
55 import static androidx.exifinterface.media.ExifInterface.TAG_DATETIME_ORIGINAL;
56 import static androidx.exifinterface.media.ExifInterface.TAG_EXIF_VERSION;
57 import static androidx.exifinterface.media.ExifInterface.TAG_EXPOSURE_BIAS_VALUE;
58 import static androidx.exifinterface.media.ExifInterface.TAG_EXPOSURE_MODE;
59 import static androidx.exifinterface.media.ExifInterface.TAG_EXPOSURE_PROGRAM;
60 import static androidx.exifinterface.media.ExifInterface.TAG_EXPOSURE_TIME;
61 import static androidx.exifinterface.media.ExifInterface.TAG_FILE_SOURCE;
62 import static androidx.exifinterface.media.ExifInterface.TAG_FLASH;
63 import static androidx.exifinterface.media.ExifInterface.TAG_FLASHPIX_VERSION;
64 import static androidx.exifinterface.media.ExifInterface.TAG_FOCAL_LENGTH;
65 import static androidx.exifinterface.media.ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT;
66 import static androidx.exifinterface.media.ExifInterface.TAG_F_NUMBER;
67 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_ALTITUDE;
68 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_ALTITUDE_REF;
69 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_DEST_BEARING_REF;
70 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_DEST_DISTANCE_REF;
71 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_IMG_DIRECTION_REF;
72 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_LATITUDE;
73 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_LATITUDE_REF;
74 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_LONGITUDE;
75 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_LONGITUDE_REF;
76 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_SPEED_REF;
77 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_TIMESTAMP;
78 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_TRACK_REF;
79 import static androidx.exifinterface.media.ExifInterface.TAG_GPS_VERSION_ID;
80 import static androidx.exifinterface.media.ExifInterface.TAG_IMAGE_LENGTH;
81 import static androidx.exifinterface.media.ExifInterface.TAG_IMAGE_WIDTH;
82 import static androidx.exifinterface.media.ExifInterface.TAG_INTEROPERABILITY_INDEX;
83 import static androidx.exifinterface.media.ExifInterface.TAG_ISO_SPEED_RATINGS;
84 import static androidx.exifinterface.media.ExifInterface.TAG_LIGHT_SOURCE;
85 import static androidx.exifinterface.media.ExifInterface.TAG_MAKE;
86 import static androidx.exifinterface.media.ExifInterface.TAG_MAX_APERTURE_VALUE;
87 import static androidx.exifinterface.media.ExifInterface.TAG_METERING_MODE;
88 import static androidx.exifinterface.media.ExifInterface.TAG_MODEL;
89 import static androidx.exifinterface.media.ExifInterface.TAG_ORIENTATION;
90 import static androidx.exifinterface.media.ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY;
91 import static androidx.exifinterface.media.ExifInterface.TAG_PIXEL_X_DIMENSION;
92 import static androidx.exifinterface.media.ExifInterface.TAG_PIXEL_Y_DIMENSION;
93 import static androidx.exifinterface.media.ExifInterface.TAG_RESOLUTION_UNIT;
94 import static androidx.exifinterface.media.ExifInterface.TAG_SATURATION;
95 import static androidx.exifinterface.media.ExifInterface.TAG_SCENE_CAPTURE_TYPE;
96 import static androidx.exifinterface.media.ExifInterface.TAG_SCENE_TYPE;
97 import static androidx.exifinterface.media.ExifInterface.TAG_SENSING_METHOD;
98 import static androidx.exifinterface.media.ExifInterface.TAG_SENSITIVITY_TYPE;
99 import static androidx.exifinterface.media.ExifInterface.TAG_SHARPNESS;
100 import static androidx.exifinterface.media.ExifInterface.TAG_SHUTTER_SPEED_VALUE;
101 import static androidx.exifinterface.media.ExifInterface.TAG_SOFTWARE;
102 import static androidx.exifinterface.media.ExifInterface.TAG_SUBSEC_TIME;
103 import static androidx.exifinterface.media.ExifInterface.TAG_SUBSEC_TIME_DIGITIZED;
104 import static androidx.exifinterface.media.ExifInterface.TAG_SUBSEC_TIME_ORIGINAL;
105 import static androidx.exifinterface.media.ExifInterface.TAG_WHITE_BALANCE;
106 import static androidx.exifinterface.media.ExifInterface.TAG_X_RESOLUTION;
107 import static androidx.exifinterface.media.ExifInterface.TAG_Y_CB_CR_POSITIONING;
108 import static androidx.exifinterface.media.ExifInterface.TAG_Y_RESOLUTION;
109 import static androidx.exifinterface.media.ExifInterface.WHITE_BALANCE_AUTO;
110 import static androidx.exifinterface.media.ExifInterface.WHITE_BALANCE_MANUAL;
111 import static androidx.exifinterface.media.ExifInterface.Y_CB_CR_POSITIONING_CENTERED;
112 
113 import android.os.Build;
114 import android.util.Pair;
115 
116 import androidx.camera.core.ImageInfo;
117 import androidx.camera.core.ImageProxy;
118 import androidx.camera.core.Logger;
119 import androidx.camera.core.impl.CameraCaptureMetaData;
120 import androidx.camera.core.impl.ImageOutputConfig;
121 import androidx.core.util.Preconditions;
122 import androidx.exifinterface.media.ExifInterface;
123 
124 import org.jspecify.annotations.NonNull;
125 import org.jspecify.annotations.Nullable;
126 
127 import java.nio.ByteOrder;
128 import java.nio.charset.StandardCharsets;
129 import java.util.Arrays;
130 import java.util.Collections;
131 import java.util.Enumeration;
132 import java.util.HashMap;
133 import java.util.HashSet;
134 import java.util.List;
135 import java.util.Locale;
136 import java.util.Map;
137 import java.util.concurrent.TimeUnit;
138 import java.util.regex.Matcher;
139 import java.util.regex.Pattern;
140 
141 /**
142  * This class stores the EXIF header in IFDs according to the JPEG specification.
143  */
144 // Note: This class is adapted from {@link androidx.exifinterface.media.ExifInterface}, and is
145 // currently expected to be used for writing a subset of Exif values. Support for other mime
146 // types besides JPEG have been removed. Support for thumbnails/strips has been removed along
147 // with many exif tags. If more tags are required, the source code for ExifInterface should be
148 // referenced and can be adapted to this class.
149 public class ExifData {
150     private static final String TAG = "ExifData";
151     private static final boolean DEBUG = false;
152 
153     /**
154      * Enum representing the white balance mode.
155      */
156     public enum WhiteBalanceMode {
157         /** AWB is turned on. */
158         AUTO,
159         /** AWB is turned off. */
160         MANUAL
161     }
162 
163     // Names for the data formats for debugging purpose.
164     static final String[] IFD_FORMAT_NAMES = new String[]{
165             "", "BYTE", "STRING", "USHORT", "ULONG", "URATIONAL", "SBYTE", "UNDEFINED", "SSHORT",
166             "SLONG", "SRATIONAL", "SINGLE", "DOUBLE", "IFD"
167     };
168 
169     /**
170      * Private tags used for pointing the other IFD offsets.
171      * The types of the following tags are int.
172      * See JEITA CP-3451C Section 4.6.3: Exif-specific IFD.
173      * For SubIFD, see Note 1 of Adobe PageMaker® 6.0 TIFF Technical Notes.
174      */
175     static final String TAG_EXIF_IFD_POINTER = "ExifIFDPointer";
176     static final String TAG_GPS_INFO_IFD_POINTER = "GPSInfoIFDPointer";
177     static final String TAG_INTEROPERABILITY_IFD_POINTER = "InteroperabilityIFDPointer";
178     static final String TAG_SUB_IFD_POINTER = "SubIFDPointer";
179 
180     // Primary image IFD TIFF tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels)
181     // This is only a subset of the tags defined in ExifInterface
182     private static final ExifTag[] IFD_TIFF_TAGS = new ExifTag[]{
183             // For below two, see TIFF 6.0 Spec Section 3: Bilevel Images.
184             new ExifTag(TAG_IMAGE_WIDTH, 256, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
185             new ExifTag(TAG_IMAGE_LENGTH, 257, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
186             new ExifTag(TAG_MAKE, 271, IFD_FORMAT_STRING),
187             new ExifTag(TAG_MODEL, 272, IFD_FORMAT_STRING),
188             new ExifTag(TAG_ORIENTATION, 274, IFD_FORMAT_USHORT),
189             new ExifTag(TAG_X_RESOLUTION, 282, IFD_FORMAT_URATIONAL),
190             new ExifTag(TAG_Y_RESOLUTION, 283, IFD_FORMAT_URATIONAL),
191             new ExifTag(TAG_RESOLUTION_UNIT, 296, IFD_FORMAT_USHORT),
192             new ExifTag(TAG_SOFTWARE, 305, IFD_FORMAT_STRING),
193             new ExifTag(TAG_DATETIME, 306, IFD_FORMAT_STRING),
194             new ExifTag(TAG_Y_CB_CR_POSITIONING, 531, IFD_FORMAT_USHORT),
195             new ExifTag(TAG_SUB_IFD_POINTER, 330, IFD_FORMAT_ULONG),
196             new ExifTag(TAG_EXIF_IFD_POINTER, 34665, IFD_FORMAT_ULONG),
197             new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG),
198     };
199 
200     // Primary image IFD Exif Private tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels)
201     // This is only a subset of the tags defined in ExifInterface
202     private static final ExifTag[] IFD_EXIF_TAGS = new ExifTag[]{
203             new ExifTag(TAG_EXPOSURE_TIME, 33434, IFD_FORMAT_URATIONAL),
204             new ExifTag(TAG_F_NUMBER, 33437, IFD_FORMAT_URATIONAL),
205             new ExifTag(TAG_EXPOSURE_PROGRAM, 34850, IFD_FORMAT_USHORT),
206             new ExifTag(TAG_PHOTOGRAPHIC_SENSITIVITY, 34855, IFD_FORMAT_USHORT),
207             new ExifTag(TAG_SENSITIVITY_TYPE, 34864, IFD_FORMAT_USHORT),
208             new ExifTag(TAG_EXIF_VERSION, 36864, IFD_FORMAT_STRING),
209             new ExifTag(TAG_DATETIME_ORIGINAL, 36867, IFD_FORMAT_STRING),
210             new ExifTag(TAG_DATETIME_DIGITIZED, 36868, IFD_FORMAT_STRING),
211             new ExifTag(TAG_COMPONENTS_CONFIGURATION, 37121, IFD_FORMAT_UNDEFINED),
212             new ExifTag(TAG_SHUTTER_SPEED_VALUE, 37377, IFD_FORMAT_SRATIONAL),
213             new ExifTag(TAG_APERTURE_VALUE, 37378, IFD_FORMAT_URATIONAL),
214             new ExifTag(TAG_BRIGHTNESS_VALUE, 37379, IFD_FORMAT_SRATIONAL),
215             new ExifTag(TAG_EXPOSURE_BIAS_VALUE, 37380, IFD_FORMAT_SRATIONAL),
216             new ExifTag(TAG_MAX_APERTURE_VALUE, 37381, IFD_FORMAT_URATIONAL),
217             new ExifTag(TAG_METERING_MODE, 37383, IFD_FORMAT_USHORT),
218             new ExifTag(TAG_LIGHT_SOURCE, 37384, IFD_FORMAT_USHORT),
219             new ExifTag(TAG_FLASH, 37385, IFD_FORMAT_USHORT),
220             new ExifTag(TAG_FOCAL_LENGTH, 37386, IFD_FORMAT_URATIONAL),
221             new ExifTag(TAG_SUBSEC_TIME, 37520, IFD_FORMAT_STRING),
222             new ExifTag(TAG_SUBSEC_TIME_ORIGINAL, 37521, IFD_FORMAT_STRING),
223             new ExifTag(TAG_SUBSEC_TIME_DIGITIZED, 37522, IFD_FORMAT_STRING),
224             new ExifTag(TAG_FLASHPIX_VERSION, 40960, IFD_FORMAT_UNDEFINED),
225             new ExifTag(TAG_COLOR_SPACE, 40961, IFD_FORMAT_USHORT),
226             new ExifTag(TAG_PIXEL_X_DIMENSION, 40962, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
227             new ExifTag(TAG_PIXEL_Y_DIMENSION, 40963, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG),
228             new ExifTag(TAG_INTEROPERABILITY_IFD_POINTER, 40965, IFD_FORMAT_ULONG),
229             new ExifTag(TAG_FOCAL_PLANE_RESOLUTION_UNIT, 41488, IFD_FORMAT_USHORT),
230             new ExifTag(TAG_SENSING_METHOD, 41495, IFD_FORMAT_USHORT),
231             new ExifTag(TAG_FILE_SOURCE, 41728, IFD_FORMAT_UNDEFINED),
232             new ExifTag(TAG_SCENE_TYPE, 41729, IFD_FORMAT_UNDEFINED),
233             new ExifTag(TAG_CUSTOM_RENDERED, 41985, IFD_FORMAT_USHORT),
234             new ExifTag(TAG_EXPOSURE_MODE, 41986, IFD_FORMAT_USHORT),
235             new ExifTag(TAG_WHITE_BALANCE, 41987, IFD_FORMAT_USHORT),
236             new ExifTag(TAG_SCENE_CAPTURE_TYPE, 41990, IFD_FORMAT_USHORT),
237             new ExifTag(TAG_CONTRAST, 41992, IFD_FORMAT_USHORT),
238             new ExifTag(TAG_SATURATION, 41993, IFD_FORMAT_USHORT),
239             new ExifTag(TAG_SHARPNESS, 41994, IFD_FORMAT_USHORT)
240     };
241 
242     // Primary image IFD GPS Info tags (See JEITA CP-3451C Section 4.6.6 Tag Support Levels)
243     // This is only a subset of the tags defined in ExifInterface
244     private static final ExifTag[] IFD_GPS_TAGS = new ExifTag[]{
245             new ExifTag(TAG_GPS_VERSION_ID, 0, IFD_FORMAT_BYTE),
246             new ExifTag(TAG_GPS_LATITUDE_REF, 1, IFD_FORMAT_STRING),
247             // Allow SRATIONAL to be compatible with apps using wrong format and
248             // even if it is negative, it may be valid latitude / longitude.
249             new ExifTag(TAG_GPS_LATITUDE, 2, IFD_FORMAT_URATIONAL, IFD_FORMAT_SRATIONAL),
250             new ExifTag(TAG_GPS_LONGITUDE_REF, 3, IFD_FORMAT_STRING),
251             new ExifTag(TAG_GPS_LONGITUDE, 4, IFD_FORMAT_URATIONAL, IFD_FORMAT_SRATIONAL),
252             new ExifTag(TAG_GPS_ALTITUDE_REF, 5, IFD_FORMAT_BYTE),
253             new ExifTag(TAG_GPS_ALTITUDE, 6, IFD_FORMAT_URATIONAL),
254             new ExifTag(TAG_GPS_TIMESTAMP, 7, IFD_FORMAT_URATIONAL),
255             new ExifTag(TAG_GPS_SPEED_REF, 12, IFD_FORMAT_STRING),
256             new ExifTag(TAG_GPS_TRACK_REF, 14, IFD_FORMAT_STRING),
257             new ExifTag(TAG_GPS_IMG_DIRECTION_REF, 16, IFD_FORMAT_STRING),
258             new ExifTag(TAG_GPS_DEST_BEARING_REF, 23, IFD_FORMAT_STRING),
259             new ExifTag(TAG_GPS_DEST_DISTANCE_REF, 25, IFD_FORMAT_STRING)
260     };
261 
262     // List of tags for pointing to the other image file directory offset.
263     static final ExifTag[] EXIF_POINTER_TAGS = new ExifTag[]{
264             new ExifTag(TAG_SUB_IFD_POINTER, 330, IFD_FORMAT_ULONG),
265             new ExifTag(TAG_EXIF_IFD_POINTER, 34665, IFD_FORMAT_ULONG),
266             new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG),
267             new ExifTag(TAG_INTEROPERABILITY_IFD_POINTER, 40965, IFD_FORMAT_ULONG),
268     };
269 
270     // Primary image IFD Interoperability tag (See JEITA CP-3451C Section 4.6.8 Tag Support Levels)
271     private static final ExifTag[] IFD_INTEROPERABILITY_TAGS = new ExifTag[]{
272             new ExifTag(TAG_INTEROPERABILITY_INDEX, 1, IFD_FORMAT_STRING)
273     };
274 
275     // List of Exif tag groups
276     static final ExifTag[][] EXIF_TAGS = new ExifTag[][]{
277             IFD_TIFF_TAGS, IFD_EXIF_TAGS, IFD_GPS_TAGS, IFD_INTEROPERABILITY_TAGS
278     };
279 
280     // Indices for the above tags. Note these must stay in sync with the order of EXIF_TAGS.
281     static final int IFD_TYPE_PRIMARY = 0;
282     static final int IFD_TYPE_EXIF = 1;
283     static final int IFD_TYPE_GPS = 2;
284     static final int IFD_TYPE_INTEROPERABILITY = 3;
285 
286     // NOTE: This is a subset of the tags from ExifInterface. Only supports tags in this class.
287     static final HashSet<String> sTagSetForCompatibility = new HashSet<>(Arrays.asList(
288             TAG_F_NUMBER, TAG_EXPOSURE_TIME, TAG_GPS_TIMESTAMP));
289 
290     private static final int MM_IN_MICRONS = 1000;
291     private static final String COMPONENTS_CONFIGURATION_YCBCR = new String(new byte[]{1, 2, 3, 0},
292             StandardCharsets.UTF_8);
293 
294     private final List<Map<String, ExifAttribute>> mAttributes;
295     private final ByteOrder mByteOrder;
296 
ExifData(ByteOrder order, List<Map<String, ExifAttribute>> attributes)297     ExifData(ByteOrder order, List<Map<String, ExifAttribute>> attributes) {
298         Preconditions.checkState(attributes.size() == EXIF_TAGS.length, "Malformed attributes "
299                 + "list. Number of IFDs mismatch.");
300         mByteOrder = order;
301         mAttributes = attributes;
302     }
303 
304     /**
305      * Creates a {@link ExifData} from {@link ImageProxy} and rotation degrees.
306      *
307      * @param rotationDegrees overwrites the rotation degrees in the {@link ImageInfo}.
308      */
create(@onNull ImageProxy imageProxy, @ImageOutputConfig.RotationDegreesValue int rotationDegrees)309     public static @NonNull ExifData create(@NonNull ImageProxy imageProxy,
310             @ImageOutputConfig.RotationDegreesValue int rotationDegrees) {
311         ExifData.Builder builder = ExifData.builderForDevice();
312         if (imageProxy.getImageInfo() != null) {
313             imageProxy.getImageInfo().populateExifData(builder);
314         }
315 
316         // Overwrites the orientation degrees value of the output image because the capture
317         // results might not have correct value when capturing image in YUV_420_888 format. See
318         // b/204375890.
319         builder.setOrientationDegrees(rotationDegrees);
320 
321         return builder.setImageWidth(imageProxy.getWidth())
322                 .setImageHeight(imageProxy.getHeight())
323                 .build();
324     }
325 
326     /**
327      * Gets the byte order.
328      */
getByteOrder()329     public @NonNull ByteOrder getByteOrder() {
330         return mByteOrder;
331     }
332 
getAttributes(int ifdIndex)333     @NonNull Map<String, ExifAttribute> getAttributes(int ifdIndex) {
334         Preconditions.checkArgumentInRange(ifdIndex, 0, EXIF_TAGS.length,
335                 "Invalid IFD index: " + ifdIndex + ". Index should be between [0, EXIF_TAGS"
336                         + ".length] ");
337         return mAttributes.get(ifdIndex);
338     }
339 
340     /**
341      * Returns the value of the specified tag or {@code null} if there
342      * is no such tag in the image file.
343      *
344      * @param tag the name of the tag.
345      */
getAttribute(@onNull String tag)346     public @Nullable String getAttribute(@NonNull String tag) {
347         ExifAttribute attribute = getExifAttribute(tag);
348         if (attribute != null) {
349             if (!sTagSetForCompatibility.contains(tag)) {
350                 return attribute.getStringValue(mByteOrder);
351             }
352             if (tag.equals(TAG_GPS_TIMESTAMP)) {
353                 // Convert the rational values to the custom formats for backwards compatibility.
354                 if (attribute.format != IFD_FORMAT_URATIONAL
355                         && attribute.format != IFD_FORMAT_SRATIONAL) {
356                     Logger.w(TAG,
357                             "GPS Timestamp format is not rational. format=" + attribute.format);
358                     return null;
359                 }
360                 LongRational[] array =
361                         (LongRational[]) attribute.getValue(mByteOrder);
362                 if (array == null || array.length != 3) {
363                     Logger.w(TAG, "Invalid GPS Timestamp array. array=" + Arrays.toString(array));
364                     return null;
365                 }
366                 return String.format(Locale.US, "%02d:%02d:%02d",
367                         (int) ((float) array[0].getNumerator() / array[0].getDenominator()),
368                         (int) ((float) array[1].getNumerator() / array[1].getDenominator()),
369                         (int) ((float) array[2].getNumerator() / array[2].getDenominator()));
370             }
371             try {
372                 return Double.toString(attribute.getDoubleValue(mByteOrder));
373             } catch (NumberFormatException e) {
374                 return null;
375             }
376         }
377         return null;
378     }
379 
380     /**
381      * Returns the EXIF attribute of the specified tag or {@code null} if there is no such tag.
382      *
383      * @param tag the name of the tag.
384      */
385     @SuppressWarnings("deprecation")
getExifAttribute(@onNull String tag)386     private @Nullable ExifAttribute getExifAttribute(@NonNull String tag) {
387         // Maintain compatibility.
388         if (TAG_ISO_SPEED_RATINGS.equals(tag)) {
389             if (DEBUG) {
390                 Logger.d(TAG, "getExifAttribute: Replacing TAG_ISO_SPEED_RATINGS with "
391                         + "TAG_PHOTOGRAPHIC_SENSITIVITY.");
392             }
393             tag = TAG_PHOTOGRAPHIC_SENSITIVITY;
394         }
395         // Retrieves all tag groups. The value from primary image tag group has a higher priority
396         // than the value from the thumbnail tag group if there are more than one candidates.
397         for (int i = 0; i < EXIF_TAGS.length; ++i) {
398             ExifAttribute value = mAttributes.get(i).get(tag);
399             if (value != null) {
400                 return value;
401             }
402         }
403         return null;
404     }
405 
406     /**
407      * Generates an empty builder suitable for generating ExifData for JPEG from the current device.
408      */
builderForDevice()409     public static @NonNull Builder builderForDevice() {
410         // Add PRIMARY defaults. EXIF and GPS defaults will be added in build()
411         return new Builder(ByteOrder.BIG_ENDIAN)
412                 .setAttribute(TAG_ORIENTATION, String.valueOf(ORIENTATION_NORMAL))
413                 .setAttribute(TAG_X_RESOLUTION, "72/1")
414                 .setAttribute(TAG_Y_RESOLUTION, "72/1")
415                 .setAttribute(TAG_RESOLUTION_UNIT, String.valueOf(RESOLUTION_UNIT_INCHES))
416                 .setAttribute(TAG_Y_CB_CR_POSITIONING,
417                         String.valueOf(Y_CB_CR_POSITIONING_CENTERED))
418                 // Defaults derived from device
419                 .setAttribute(TAG_MAKE, Build.MANUFACTURER)
420                 .setAttribute(TAG_MODEL, Build.MODEL);
421     }
422 
423     /**
424      * Builder for the {@link ExifData} class.
425      */
426     public static final class Builder {
427         // Pattern to check gps timestamp
428         private static final Pattern GPS_TIMESTAMP_PATTERN =
429                 Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2})$");
430         // Pattern to check date time primary format (e.g. 2020:01:01 00:00:00)
431         private static final Pattern DATETIME_PRIMARY_FORMAT_PATTERN =
432                 Pattern.compile("^(\\d{4}):(\\d{2}):(\\d{2})\\s(\\d{2}):(\\d{2}):(\\d{2})$");
433         // Pattern to check date time secondary format (e.g. 2020-01-01 00:00:00)
434         private static final Pattern DATETIME_SECONDARY_FORMAT_PATTERN =
435                 Pattern.compile("^(\\d{4})-(\\d{2})-(\\d{2})\\s(\\d{2}):(\\d{2}):(\\d{2})$");
436         private static final int DATETIME_VALUE_STRING_LENGTH = 19;
437 
438         // Mappings from tag name to tag number and each item represents one IFD tag group.
439         static final List<HashMap<String, ExifTag>> sExifTagMapsForWriting =
440                 Collections.list(new Enumeration<HashMap<String, ExifTag>>() {
441                     int mIfdIndex = 0;
442 
443                     @Override
444                     public boolean hasMoreElements() {
445                         return mIfdIndex < EXIF_TAGS.length;
446                     }
447 
448                     @Override
449                     public HashMap<String, ExifTag> nextElement() {
450                         // Build up the hash tables to look up Exif tags for writing Exif tags.
451                         HashMap<String, ExifTag> map = new HashMap<>();
452                         for (ExifTag tag : EXIF_TAGS[mIfdIndex]) {
453                             map.put(tag.name, tag);
454                         }
455                         mIfdIndex++;
456                         return map;
457                     }
458                 });
459 
460         final List<Map<String, ExifAttribute>> mAttributes = Collections.list(
461                 new Enumeration<Map<String, ExifAttribute>>() {
462                     int mIfdIndex = 0;
463 
464                     @Override
465                     public boolean hasMoreElements() {
466                         return mIfdIndex < EXIF_TAGS.length;
467                     }
468 
469                     @Override
470                     public Map<String, ExifAttribute> nextElement() {
471                         mIfdIndex++;
472                         return new HashMap<>();
473                     }
474                 });
475         private final ByteOrder mByteOrder;
476 
Builder(@onNull ByteOrder byteOrder)477         Builder(@NonNull ByteOrder byteOrder) {
478             mByteOrder = byteOrder;
479         }
480 
481         /**
482          * Sets the width of the image.
483          *
484          * @param width the width of the image.
485          */
setImageWidth(int width)486         public @NonNull Builder setImageWidth(int width) {
487             return setAttribute(TAG_IMAGE_WIDTH, String.valueOf(width));
488         }
489 
490         /**
491          * Sets the height of the image.
492          *
493          * @param height the height of the image.
494          */
setImageHeight(int height)495         public @NonNull Builder setImageHeight(int height) {
496             return setAttribute(TAG_IMAGE_LENGTH, String.valueOf(height));
497         }
498 
499         /**
500          * Sets the orientation of the image in degrees.
501          *
502          * @param orientationDegrees the orientation in degrees. Can be one of (0, 90, 180, 270)
503          */
setOrientationDegrees(int orientationDegrees)504         public @NonNull Builder setOrientationDegrees(int orientationDegrees) {
505             int orientationEnum;
506             switch (orientationDegrees) {
507                 case 0:
508                     orientationEnum = ExifInterface.ORIENTATION_NORMAL;
509                     break;
510                 case 90:
511                     orientationEnum = ExifInterface.ORIENTATION_ROTATE_90;
512                     break;
513                 case 180:
514                     orientationEnum = ExifInterface.ORIENTATION_ROTATE_180;
515                     break;
516                 case 270:
517                     orientationEnum = ExifInterface.ORIENTATION_ROTATE_270;
518                     break;
519                 default:
520                     Logger.w(TAG,
521                             "Unexpected orientation value: " + orientationDegrees
522                                     + ". Must be one of 0, 90, 180, 270.");
523                     orientationEnum = ExifInterface.ORIENTATION_UNDEFINED;
524                     break;
525             }
526             return setAttribute(TAG_ORIENTATION, String.valueOf(orientationEnum));
527         }
528 
529         /**
530          * Sets the flash information from
531          * {@link androidx.camera.core.impl.CameraCaptureMetaData.FlashState}.
532          *
533          * @param flashState the state of the flash at capture time.
534          */
setFlashState( CameraCaptureMetaData.@onNull FlashState flashState)535         public @NonNull Builder setFlashState(
536                 CameraCaptureMetaData.@NonNull FlashState flashState) {
537             if (flashState == CameraCaptureMetaData.FlashState.UNKNOWN) {
538                 // Cannot set flash state information
539                 return this;
540             }
541 
542             short value;
543             switch (flashState) {
544                 case READY:
545                     value = 0;
546                     break;
547                 case NONE:
548                     value = FLAG_FLASH_NO_FLASH_FUNCTION;
549                     break;
550                 case FIRED:
551                     value = FLAG_FLASH_FIRED;
552                     break;
553                 default:
554                     Logger.w(TAG, "Unknown flash state: " + flashState);
555                     return this;
556             }
557 
558             if ((value & FLAG_FLASH_FIRED) == FLAG_FLASH_FIRED) {
559                 // Set light source to flash
560                 setAttribute(TAG_LIGHT_SOURCE, String.valueOf(LIGHT_SOURCE_FLASH));
561             }
562 
563 
564             return setAttribute(TAG_FLASH, String.valueOf(value));
565         }
566 
567         /**
568          * Sets the amount of time the sensor was exposed for, in nanoseconds.
569          *
570          * @param exposureTimeNs The exposure time in nanoseconds.
571          */
setExposureTimeNanos(long exposureTimeNs)572         public @NonNull Builder setExposureTimeNanos(long exposureTimeNs) {
573             return setAttribute(TAG_EXPOSURE_TIME,
574                     String.valueOf(exposureTimeNs / (double) TimeUnit.SECONDS.toNanos(1)));
575         }
576 
577         /**
578          * Sets the lens f-number.
579          *
580          * <p>The lens f-number has precision 1.xx, for example, 1.80.
581          *
582          * @param fNumber The f-number.
583          */
setLensFNumber(float fNumber)584         public @NonNull Builder setLensFNumber(float fNumber) {
585             return setAttribute(TAG_F_NUMBER, String.valueOf(fNumber));
586         }
587 
588         /**
589          * Sets the ISO.
590          *
591          * @param iso the standard ISO sensitivity value, as defined in ISO 12232:2006.
592          */
setIso(int iso)593         public @NonNull Builder setIso(int iso) {
594             return setAttribute(TAG_SENSITIVITY_TYPE, String.valueOf(SENSITIVITY_TYPE_ISO_SPEED))
595                     .setAttribute(TAG_PHOTOGRAPHIC_SENSITIVITY, String.valueOf(Math.min(65535,
596                             iso)));
597         }
598 
599         /**
600          * Sets lens focal length, in millimeters.
601          *
602          * @param focalLength The lens focal length in millimeters.
603          */
setFocalLength(float focalLength)604         public @NonNull Builder setFocalLength(float focalLength) {
605             LongRational focalLengthRational =
606                     new LongRational((long) (focalLength * MM_IN_MICRONS), MM_IN_MICRONS);
607             return setAttribute(TAG_FOCAL_LENGTH, focalLengthRational.toString());
608         }
609 
610         /**
611          * Sets the white balance mode.
612          *
613          * @param whiteBalanceMode The white balance mode. One of {@link WhiteBalanceMode#AUTO}
614          *                         or {@link WhiteBalanceMode#MANUAL}.
615          */
setWhiteBalanceMode(@onNull WhiteBalanceMode whiteBalanceMode)616         public @NonNull Builder setWhiteBalanceMode(@NonNull WhiteBalanceMode whiteBalanceMode) {
617             String wbString = null;
618             switch (whiteBalanceMode) {
619                 case AUTO:
620                     wbString = String.valueOf(WHITE_BALANCE_AUTO);
621                     break;
622                 case MANUAL:
623                     wbString = String.valueOf(WHITE_BALANCE_MANUAL);
624                     break;
625             }
626             return setAttribute(TAG_WHITE_BALANCE, wbString);
627         }
628 
629         /**
630          * Sets the value of the specified tag.
631          *
632          * @param tag   the name of the tag.
633          * @param value the value of the tag.
634          */
setAttribute(@onNull String tag, @NonNull String value)635         public @NonNull Builder setAttribute(@NonNull String tag, @NonNull String value) {
636             setAttributeInternal(tag, value, mAttributes);
637             return this;
638         }
639 
640         /**
641          * Removes the attribute with the given tag.
642          *
643          * @param tag the name of the tag.
644          */
removeAttribute(@onNull String tag)645         public @NonNull Builder removeAttribute(@NonNull String tag) {
646             setAttributeInternal(tag, null, mAttributes);
647             return this;
648         }
649 
setAttributeIfMissing(@onNull String tag, @NonNull String value, @NonNull List<Map<String, ExifAttribute>> attributes)650         private void setAttributeIfMissing(@NonNull String tag, @NonNull String value,
651                 @NonNull List<Map<String, ExifAttribute>> attributes) {
652             for (Map<String, ExifAttribute> attrs : attributes) {
653                 if (attrs.containsKey(tag)) {
654                     // Attr already exists
655                     return;
656                 }
657             }
658 
659             // Add missing attribute.
660             setAttributeInternal(tag, value, attributes);
661         }
662 
663         @SuppressWarnings("deprecation")
664         // Allows null values to remove attributes
setAttributeInternal(@onNull String tag, @Nullable String value, @NonNull List<Map<String, ExifAttribute>> attributes)665         private void setAttributeInternal(@NonNull String tag, @Nullable String value,
666                 @NonNull List<Map<String, ExifAttribute>> attributes) {
667             // Validate and convert if necessary.
668             if (TAG_DATETIME.equals(tag) || TAG_DATETIME_ORIGINAL.equals(tag)
669                     || TAG_DATETIME_DIGITIZED.equals(tag)) {
670                 if (value != null) {
671                     boolean isPrimaryFormat = DATETIME_PRIMARY_FORMAT_PATTERN.matcher(value).find();
672                     boolean isSecondaryFormat = DATETIME_SECONDARY_FORMAT_PATTERN.matcher(
673                             value).find();
674                     // Validate
675                     if (value.length() != DATETIME_VALUE_STRING_LENGTH
676                             || (!isPrimaryFormat && !isSecondaryFormat)) {
677                         Logger.w(TAG, "Invalid value for " + tag + " : " + value);
678                         return;
679                     }
680                     // If datetime value has secondary format (e.g. 2020-01-01 00:00:00), convert it
681                     // to primary format (e.g. 2020:01:01 00:00:00) since it is the format in the
682                     // official documentation.
683                     // See JEITA CP-3451C Section 4.6.4. D. Other Tags, DateTime
684                     if (isSecondaryFormat) {
685                         // Replace "-" with ":" to match the primary format.
686                         value = value.replaceAll("-", ":");
687                     }
688                 }
689             }
690             // Maintain compatibility.
691             if (TAG_ISO_SPEED_RATINGS.equals(tag)) {
692                 if (DEBUG) {
693                     Logger.d(TAG, "setAttribute: Replacing TAG_ISO_SPEED_RATINGS with "
694                             + "TAG_PHOTOGRAPHIC_SENSITIVITY.");
695                 }
696                 tag = TAG_PHOTOGRAPHIC_SENSITIVITY;
697             }
698             // Convert the given value to rational values for backwards compatibility.
699             if (value != null && sTagSetForCompatibility.contains(tag)) {
700                 if (tag.equals(TAG_GPS_TIMESTAMP)) {
701                     Matcher m = GPS_TIMESTAMP_PATTERN.matcher(value);
702                     if (!m.find()) {
703                         Logger.w(TAG, "Invalid value for " + tag + " : " + value);
704                         return;
705                     }
706                     value = Integer.parseInt(Preconditions.checkNotNull(m.group(1))) + "/1,"
707                             + Integer.parseInt(Preconditions.checkNotNull(m.group(2))) + "/1,"
708                             + Integer.parseInt(Preconditions.checkNotNull(m.group(3))) + "/1";
709                 } else {
710                     try {
711                         double doubleValue = Double.parseDouble(value);
712                         value = new LongRational(doubleValue).toString();
713                     } catch (NumberFormatException e) {
714                         Logger.w(TAG, "Invalid value for " + tag + " : " + value, e);
715                         return;
716                     }
717                 }
718             }
719 
720             for (int i = 0; i < EXIF_TAGS.length; ++i) {
721                 final ExifTag exifTag = sExifTagMapsForWriting.get(i).get(tag);
722                 if (exifTag != null) {
723                     if (value == null) {
724                         attributes.get(i).remove(tag);
725                         continue;
726                     }
727                     Pair<Integer, Integer> guess = guessDataFormat(value);
728                     int dataFormat;
729                     if (exifTag.primaryFormat == guess.first
730                             || exifTag.primaryFormat == guess.second) {
731                         dataFormat = exifTag.primaryFormat;
732                     } else if (exifTag.secondaryFormat != -1 && (
733                             exifTag.secondaryFormat == guess.first
734                                     || exifTag.secondaryFormat == guess.second)) {
735                         dataFormat = exifTag.secondaryFormat;
736                     } else if (exifTag.primaryFormat == IFD_FORMAT_BYTE
737                             || exifTag.primaryFormat == IFD_FORMAT_UNDEFINED
738                             || exifTag.primaryFormat == IFD_FORMAT_STRING) {
739                         dataFormat = exifTag.primaryFormat;
740                     } else {
741                         if (DEBUG) {
742                             Logger.d(TAG, "Given tag (" + tag
743                                     + ") value didn't match with one of expected "
744                                     + "formats: " + IFD_FORMAT_NAMES[exifTag.primaryFormat]
745                                     + (exifTag.secondaryFormat == -1 ? "" : ", "
746                                     + IFD_FORMAT_NAMES[exifTag.secondaryFormat]) + " (guess: "
747                                     + IFD_FORMAT_NAMES[guess.first] + (guess.second == -1 ? ""
748                                     : ", "
749                                             + IFD_FORMAT_NAMES[guess.second]) + ")");
750                         }
751                         continue;
752                     }
753                     switch (dataFormat) {
754                         case IFD_FORMAT_BYTE: {
755                             attributes.get(i).put(tag, ExifAttribute.createByte(value));
756                             break;
757                         }
758                         case IFD_FORMAT_UNDEFINED:
759                         case IFD_FORMAT_STRING: {
760                             attributes.get(i).put(tag, ExifAttribute.createString(value));
761                             break;
762                         }
763                         case IFD_FORMAT_USHORT: {
764                             final String[] values = value.split(",", -1);
765                             final int[] intArray = new int[values.length];
766                             for (int j = 0; j < values.length; ++j) {
767                                 intArray[j] = Integer.parseInt(values[j]);
768                             }
769                             attributes.get(i).put(tag,
770                                     ExifAttribute.createUShort(intArray, mByteOrder));
771                             break;
772                         }
773                         case IFD_FORMAT_SLONG: {
774                             final String[] values = value.split(",", -1);
775                             final int[] intArray = new int[values.length];
776                             for (int j = 0; j < values.length; ++j) {
777                                 intArray[j] = Integer.parseInt(values[j]);
778                             }
779                             attributes.get(i).put(tag,
780                                     ExifAttribute.createSLong(intArray, mByteOrder));
781                             break;
782                         }
783                         case IFD_FORMAT_ULONG: {
784                             final String[] values = value.split(",", -1);
785                             final long[] longArray = new long[values.length];
786                             for (int j = 0; j < values.length; ++j) {
787                                 longArray[j] = Long.parseLong(values[j]);
788                             }
789                             attributes.get(i).put(tag,
790                                     ExifAttribute.createULong(longArray, mByteOrder));
791                             break;
792                         }
793                         case IFD_FORMAT_URATIONAL: {
794                             final String[] values = value.split(",", -1);
795                             final LongRational[] rationalArray = new LongRational[values.length];
796                             for (int j = 0; j < values.length; ++j) {
797                                 final String[] numbers = values[j].split("/", -1);
798                                 rationalArray[j] = new LongRational(
799                                         (long) Double.parseDouble(numbers[0]),
800                                         (long) Double.parseDouble(numbers[1]));
801                             }
802                             attributes.get(i).put(tag,
803                                     ExifAttribute.createURational(rationalArray, mByteOrder));
804                             break;
805                         }
806                         case IFD_FORMAT_SRATIONAL: {
807                             final String[] values = value.split(",", -1);
808                             final LongRational[] rationalArray = new LongRational[values.length];
809                             for (int j = 0; j < values.length; ++j) {
810                                 final String[] numbers = values[j].split("/", -1);
811                                 rationalArray[j] = new LongRational(
812                                         (long) Double.parseDouble(numbers[0]),
813                                         (long) Double.parseDouble(numbers[1]));
814                             }
815                             attributes.get(i).put(tag,
816                                     ExifAttribute.createSRational(rationalArray, mByteOrder));
817                             break;
818                         }
819                         case IFD_FORMAT_DOUBLE: {
820                             final String[] values = value.split(",", -1);
821                             final double[] doubleArray = new double[values.length];
822                             for (int j = 0; j < values.length; ++j) {
823                                 doubleArray[j] = Double.parseDouble(values[j]);
824                             }
825                             attributes.get(i).put(tag,
826                                     ExifAttribute.createDouble(doubleArray, mByteOrder));
827                             break;
828                         }
829                         default:
830                             if (DEBUG) {
831                                 Logger.d(TAG,
832                                         "Data format isn't one of expected formats: " + dataFormat);
833                             }
834                     }
835                 }
836             }
837         }
838 
839         /**
840          * Builds an {@link ExifData} from the current state of the builder.
841          */
build()842         public @NonNull ExifData build() {
843             // Create a read-only copy of all attributes. This needs to be a deep copy since
844             // build() can be called multiple times. We'll remove null values as well.
845             List<Map<String, ExifAttribute>> attributes = Collections.list(
846                     new Enumeration<Map<String, ExifAttribute>>() {
847                         final Enumeration<Map<String, ExifAttribute>> mMapEnumeration =
848                                 Collections.enumeration(mAttributes);
849 
850                         @Override
851                         public boolean hasMoreElements() {
852                             return mMapEnumeration.hasMoreElements();
853                         }
854 
855                         @Override
856                         public Map<String, ExifAttribute> nextElement() {
857                             return new HashMap<>(mMapEnumeration.nextElement());
858                         }
859                     });
860             // Add EXIF defaults if needed
861             if (!attributes.get(IFD_TYPE_EXIF).isEmpty()) {
862                 setAttributeIfMissing(TAG_EXPOSURE_PROGRAM,
863                         String.valueOf(EXPOSURE_PROGRAM_NOT_DEFINED), attributes);
864                 setAttributeIfMissing(TAG_EXIF_VERSION, "0230", attributes);
865                 // Default is for YCbCr components
866                 setAttributeIfMissing(TAG_COMPONENTS_CONFIGURATION, COMPONENTS_CONFIGURATION_YCBCR,
867                         attributes);
868                 setAttributeIfMissing(TAG_METERING_MODE, String.valueOf(METERING_MODE_UNKNOWN),
869                         attributes);
870                 setAttributeIfMissing(TAG_LIGHT_SOURCE, String.valueOf(LIGHT_SOURCE_UNKNOWN),
871                         attributes);
872                 setAttributeIfMissing(TAG_FLASHPIX_VERSION, "0100", attributes);
873                 setAttributeIfMissing(TAG_FOCAL_PLANE_RESOLUTION_UNIT,
874                         String.valueOf(RESOLUTION_UNIT_INCHES), attributes);
875                 setAttributeIfMissing(TAG_FILE_SOURCE, String.valueOf(FILE_SOURCE_DSC), attributes);
876                 setAttributeIfMissing(TAG_SCENE_TYPE,
877                         String.valueOf(SCENE_TYPE_DIRECTLY_PHOTOGRAPHED), attributes);
878                 setAttributeIfMissing(TAG_CUSTOM_RENDERED, String.valueOf(RENDERED_PROCESS_NORMAL),
879                         attributes);
880                 setAttributeIfMissing(TAG_SCENE_CAPTURE_TYPE,
881                         String.valueOf(SCENE_CAPTURE_TYPE_STANDARD), attributes);
882                 setAttributeIfMissing(TAG_CONTRAST, String.valueOf(CONTRAST_NORMAL), attributes);
883                 setAttributeIfMissing(TAG_SATURATION, String.valueOf(SATURATION_NORMAL),
884                         attributes);
885                 setAttributeIfMissing(TAG_SHARPNESS, String.valueOf(SHARPNESS_NORMAL), attributes);
886             }
887             // Add GPS defaults if needed
888             if (!attributes.get(IFD_TYPE_GPS).isEmpty()) {
889                 setAttributeIfMissing(TAG_GPS_VERSION_ID, "2300", attributes);
890                 setAttributeIfMissing(TAG_GPS_SPEED_REF, GPS_SPEED_KILOMETERS_PER_HOUR, attributes);
891                 setAttributeIfMissing(TAG_GPS_TRACK_REF, GPS_DIRECTION_TRUE, attributes);
892                 setAttributeIfMissing(TAG_GPS_IMG_DIRECTION_REF, GPS_DIRECTION_TRUE, attributes);
893                 setAttributeIfMissing(TAG_GPS_DEST_BEARING_REF, GPS_DIRECTION_TRUE, attributes);
894                 setAttributeIfMissing(TAG_GPS_DEST_DISTANCE_REF, GPS_DISTANCE_KILOMETERS,
895                         attributes);
896             }
897             return new ExifData(mByteOrder, attributes);
898         }
899 
900         /**
901          * Determines the data format of EXIF entry value.
902          *
903          * @param entryValue The value to be determined.
904          * @return Returns two data formats guessed as a pair in integer. If there is no two
905          * candidate
906          * data formats for the given entry value, returns {@code -1} in the second of the pair.
907          */
guessDataFormat(String entryValue)908         private static Pair<Integer, Integer> guessDataFormat(String entryValue) {
909             // See TIFF 6.0 Section 2, "Image File Directory".
910             // Take the first component if there are more than one component.
911             if (entryValue.contains(",")) {
912                 String[] entryValues = entryValue.split(",", -1);
913                 Pair<Integer, Integer> dataFormat = guessDataFormat(entryValues[0]);
914                 if (dataFormat.first == IFD_FORMAT_STRING) {
915                     return dataFormat;
916                 }
917                 for (int i = 1; i < entryValues.length; ++i) {
918                     final Pair<Integer, Integer> guessDataFormat = guessDataFormat(entryValues[i]);
919                     int first = -1, second = -1;
920                     if (guessDataFormat.first.equals(dataFormat.first)
921                             || guessDataFormat.second.equals(dataFormat.first)) {
922                         first = dataFormat.first;
923                     }
924                     if (dataFormat.second != -1 && (guessDataFormat.first.equals(dataFormat.second)
925                             || guessDataFormat.second.equals(dataFormat.second))) {
926                         second = dataFormat.second;
927                     }
928                     if (first == -1 && second == -1) {
929                         return new Pair<>(IFD_FORMAT_STRING, -1);
930                     }
931                     if (first == -1) {
932                         dataFormat = new Pair<>(second, -1);
933                         continue;
934                     }
935                     if (second == -1) {
936                         dataFormat = new Pair<>(first, -1);
937                     }
938                 }
939                 return dataFormat;
940             }
941 
942             if (entryValue.contains("/")) {
943                 String[] rationalNumber = entryValue.split("/", -1);
944                 if (rationalNumber.length == 2) {
945                     try {
946                         long numerator = (long) Double.parseDouble(rationalNumber[0]);
947                         long denominator = (long) Double.parseDouble(rationalNumber[1]);
948                         if (numerator < 0L || denominator < 0L) {
949                             return new Pair<>(IFD_FORMAT_SRATIONAL, -1);
950                         }
951                         if (numerator > Integer.MAX_VALUE || denominator > Integer.MAX_VALUE) {
952                             return new Pair<>(IFD_FORMAT_URATIONAL, -1);
953                         }
954                         return new Pair<>(IFD_FORMAT_SRATIONAL, IFD_FORMAT_URATIONAL);
955                     } catch (NumberFormatException e) {
956                         // Ignored
957                     }
958                 }
959                 return new Pair<>(IFD_FORMAT_STRING, -1);
960             }
961             try {
962                 long longValue = Long.parseLong(entryValue);
963                 if (longValue >= 0 && longValue <= 65535) {
964                     return new Pair<>(IFD_FORMAT_USHORT, IFD_FORMAT_ULONG);
965                 }
966                 if (longValue < 0) {
967                     return new Pair<>(IFD_FORMAT_SLONG, -1);
968                 }
969                 return new Pair<>(IFD_FORMAT_ULONG, -1);
970             } catch (NumberFormatException e) {
971                 // Ignored
972             }
973             try {
974                 Double.parseDouble(entryValue);
975                 return new Pair<>(IFD_FORMAT_DOUBLE, -1);
976             } catch (NumberFormatException e) {
977                 // Ignored
978             }
979             return new Pair<>(IFD_FORMAT_STRING, -1);
980         }
981     }
982 }
983