java.lang.Object
↳androidx.camera.core.impl.utils.Exif
Gradle dependencies
compile group: 'androidx.camera', name: 'camera-core', version: '1.5.0-alpha01'
- groupId: androidx.camera
- artifactId: camera-core
- version: 1.5.0-alpha01
Artifact androidx.camera:camera-core:1.5.0-alpha01 it located at Google repository (https://maven.google.com/)
Overview
Utility class for modifying metadata on JPEG files.
Call Exif.save() to persist changes to disc.
Summary
Fields
public static final long
INVALID_TIMESTAMPTimestamp value indicating a timestamp value that is either not set or not valid
public static final java.lang.String
TAG_THUMBNAIL_ORIENTATIONMethods
public static
Exif createFromFile(java.io.File file)
Returns an Exif from the exif data contained in the file.
Parameters:
file: the file to read exif data from
Returns an Exif extracted from the given ImageProxy.
This method rewinds and reads the given buffer.
public static
Exif createFromFileString(java.lang.String filePath)
Returns an Exif from the exif data contained in the file at the filePath
Parameters:
filePath: the path to the file to read exif data from
public static
Exif createFromInputStream(java.io.InputStream is)
Returns an Exif from the exif data contain in the input stream.
Parameters:
is: the input stream to read exif data from
Persists changes to disc.
public void
copyToCroppedImage(
Exif croppedExif)
Copies Exif values to the given instance.
This methods is for copying exif data from the original image to the cropped image. Tags
affected by cropping are not copied.
public java.lang.String
toString()
public int
getOrientation()
public void
setOrientation(int orientation)
Sets the orientation for the exif.
Returns the width of the photo in pixels.
Returns the height of the photo in pixels.
public java.lang.String
getDescription()
public void
setDescription(java.lang.String description)
Sets the description for the exif.
Returns:
The degree of rotation (eg. 0, 90, 180, 270).
public boolean
isFlippedVertically()
Returns:
True if the image is flipped vertically after rotation.
public boolean
isFlippedHorizontally()
Returns:
True if the image is flipped horizontally after rotation.
public long
getLastModifiedTimestamp()
Returns:
The timestamp (in millis) that this picture was modified, or Exif.INVALID_TIMESTAMP if no time is available.
public long
getTimestamp()
Returns:
The timestamp (in millis) that this picture was taken, or Exif.INVALID_TIMESTAMP
if no time is available.
public Location
getLocation()
Returns:
The location this picture was taken, or null if no location is available.
public void
rotate(int degrees)
Rotates the image by the given degrees. Can only rotate by right angles (eg. 90, 180, -90).
Other increments will set the orientation to undefined.
public void
flipVertically()
Sets attributes to represent a flip of the image over the horizon so that the top and bottom
are reversed.
public void
flipHorizontally()
Sets attributes to represent a flip of the image over the vertical so that the left and right
are reversed.
public java.lang.String
getMetadata()
public void
attachTimestamp()
Attaches the current timestamp to the file.
public void
removeTimestamp()
Removes the timestamp from the file.
public void
attachLocation(Location location)
Attaches the given location to the file.
public void
removeLocation()
Removes the location from the file.
public static java.util.List<java.lang.String>
getAllExifTags()
Creates a list that contains all public tags defined in ExifInterface.
Deprecated tags are not included.
Source
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.camera.core.impl.utils;
import android.location.Location;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.camera.core.ImageProxy;
import androidx.camera.core.Logger;
import androidx.exifinterface.media.ExifInterface;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
/**
* Utility class for modifying metadata on JPEG files.
*
* <p>Call {@link #save()} to persist changes to disc.
*/
public final class Exif {
/** Timestamp value indicating a timestamp value that is either not set or not valid */
public static final long INVALID_TIMESTAMP = -1;
// Forked from ExifInterface.TAG_THUMBNAIL_ORIENTATION. The value is library-internal so we
// can't depend on it directly.
public static final String TAG_THUMBNAIL_ORIENTATION = "ThumbnailOrientation";
private static final String TAG = Exif.class.getSimpleName();
private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT =
new ThreadLocal<SimpleDateFormat>() {
@Override
public SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy:MM:dd", Locale.US);
}
};
private static final ThreadLocal<SimpleDateFormat> TIME_FORMAT =
new ThreadLocal<SimpleDateFormat>() {
@Override
public SimpleDateFormat initialValue() {
return new SimpleDateFormat("HH:mm:ss", Locale.US);
}
};
private static final ThreadLocal<SimpleDateFormat> DATETIME_FORMAT =
new ThreadLocal<SimpleDateFormat>() {
@Override
public SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy:MM:dd HH:mm:ss", Locale.US);
}
};
private static final String KILOMETERS_PER_HOUR = "K";
private static final String MILES_PER_HOUR = "M";
private static final String KNOTS = "N";
/** All public tags in {@link ExifInterface}. */
private static final List<String> ALL_EXIF_TAGS = getAllExifTags();
// Exif tags that should not be copied to the cropped image.
private static final List<String> DO_NOT_COPY_EXIF_TAGS = Arrays.asList(
// Dimension-related tags, which might change after cropping.
ExifInterface.TAG_IMAGE_WIDTH,
ExifInterface.TAG_IMAGE_LENGTH,
ExifInterface.TAG_PIXEL_X_DIMENSION,
ExifInterface.TAG_PIXEL_Y_DIMENSION,
// Thumbnail-related tags. Currently we do not create thumbnail for cropped images.
ExifInterface.TAG_COMPRESSION, // Our primary image is always Jpeg.
ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT,
ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
ExifInterface.TAG_THUMBNAIL_IMAGE_LENGTH,
ExifInterface.TAG_THUMBNAIL_IMAGE_WIDTH,
TAG_THUMBNAIL_ORIENTATION);
private final ExifInterface mExifInterface;
// When true, avoid saving any time. This is a privacy issue.
private boolean mRemoveTimestamp = false;
private Exif(ExifInterface exifInterface) {
mExifInterface = exifInterface;
}
/**
* Returns an Exif from the exif data contained in the file.
*
* @param file the file to read exif data from
*/
@NonNull
public static Exif createFromFile(@NonNull File file) throws IOException {
return createFromFileString(file.toString());
}
/**
* Returns an Exif extracted from the given {@link ImageProxy}.
*
* <p> This method rewinds and reads the given buffer.
*/
@NonNull
public static Exif createFromImageProxy(@NonNull ImageProxy imageProxy) throws IOException {
ByteBuffer buffer = imageProxy.getPlanes()[0].getBuffer();
// Rewind to make sure it is at the beginning of the buffer
buffer.rewind();
byte[] data = new byte[buffer.capacity()];
buffer.get(data);
InputStream inputStream = new ByteArrayInputStream(data);
return Exif.createFromInputStream(inputStream);
}
/**
* Returns an Exif from the exif data contained in the file at the filePath
*
* @param filePath the path to the file to read exif data from
*/
@NonNull
public static Exif createFromFileString(@NonNull String filePath) throws IOException {
return new Exif(new ExifInterface(filePath));
}
/**
* Returns an Exif from the exif data contain in the input stream.
*
* @param is the input stream to read exif data from
*/
@NonNull
public static Exif createFromInputStream(@NonNull InputStream is) throws IOException {
return new Exif(new ExifInterface(is));
}
private static String convertToExifDateTime(long timestamp) {
return DATETIME_FORMAT.get().format(new Date(timestamp));
}
private static Date convertFromExifDateTime(String dateTime) throws ParseException {
return DATETIME_FORMAT.get().parse(dateTime);
}
private static Date convertFromExifDate(String date) throws ParseException {
return DATE_FORMAT.get().parse(date);
}
private static Date convertFromExifTime(String time) throws ParseException {
return TIME_FORMAT.get().parse(time);
}
/** Persists changes to disc. */
public void save() throws IOException {
if (!mRemoveTimestamp) {
attachLastModifiedTimestamp();
}
mExifInterface.saveAttributes();
}
/**
* Copies Exif values to the given instance.
*
* <p> This methods is for copying exif data from the original image to the cropped image. Tags
* affected by cropping are not copied.
*/
public void copyToCroppedImage(@NonNull Exif croppedExif) {
List<String> exifTags = new ArrayList<>(ALL_EXIF_TAGS);
exifTags.removeAll(DO_NOT_COPY_EXIF_TAGS);
for (String tag : exifTags) {
String originalValue = mExifInterface.getAttribute(tag);
String croppedExifValue = croppedExif.mExifInterface.getAttribute(tag);
if (originalValue != null && !Objects.equals(originalValue, croppedExifValue)) {
croppedExif.mExifInterface.setAttribute(tag, originalValue);
}
}
}
@Override
public String toString() {
return String.format(
Locale.ENGLISH,
"Exif{width=%s, height=%s, rotation=%d, "
+ "isFlippedVertically=%s, isFlippedHorizontally=%s, location=%s, "
+ "timestamp=%s, description=%s}",
getWidth(),
getHeight(),
getRotation(),
isFlippedVertically(),
isFlippedHorizontally(),
getLocation(),
getTimestamp(),
getDescription());
}
public int getOrientation() {
return mExifInterface.getAttributeInt(
ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
}
/** Sets the orientation for the exif. */
public void setOrientation(int orientation) {
mExifInterface.setAttribute(ExifInterface.TAG_ORIENTATION, String.valueOf(orientation));
}
/** Returns the width of the photo in pixels. */
public int getWidth() {
return mExifInterface.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, 0);
}
/** Returns the height of the photo in pixels. */
public int getHeight() {
return mExifInterface.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, 0);
}
@Nullable
public String getDescription() {
return mExifInterface.getAttribute(ExifInterface.TAG_IMAGE_DESCRIPTION);
}
/** Sets the description for the exif. */
public void setDescription(@Nullable String description) {
mExifInterface.setAttribute(ExifInterface.TAG_IMAGE_DESCRIPTION, description);
}
/** @return The degree of rotation (eg. 0, 90, 180, 270). */
public int getRotation() {
switch (getOrientation()) {
case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
return 0;
case ExifInterface.ORIENTATION_ROTATE_180:
return 180;
case ExifInterface.ORIENTATION_FLIP_VERTICAL:
return 180;
case ExifInterface.ORIENTATION_TRANSPOSE:
return 270;
case ExifInterface.ORIENTATION_ROTATE_90:
return 90;
case ExifInterface.ORIENTATION_TRANSVERSE:
return 90;
case ExifInterface.ORIENTATION_ROTATE_270:
return 270;
case ExifInterface.ORIENTATION_NORMAL:
// Fall-through
case ExifInterface.ORIENTATION_UNDEFINED:
// Fall-through
default:
return 0;
}
}
/** @return True if the image is flipped vertically after rotation. */
public boolean isFlippedVertically() {
switch (getOrientation()) {
case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
return false;
case ExifInterface.ORIENTATION_ROTATE_180:
return false;
case ExifInterface.ORIENTATION_FLIP_VERTICAL:
return true;
case ExifInterface.ORIENTATION_TRANSPOSE:
return true;
case ExifInterface.ORIENTATION_ROTATE_90:
return false;
case ExifInterface.ORIENTATION_TRANSVERSE:
return true;
case ExifInterface.ORIENTATION_ROTATE_270:
return false;
case ExifInterface.ORIENTATION_NORMAL:
// Fall-through
case ExifInterface.ORIENTATION_UNDEFINED:
// Fall-through
default:
return false;
}
}
/** @return True if the image is flipped horizontally after rotation. */
public boolean isFlippedHorizontally() {
switch (getOrientation()) {
case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
return true;
case ExifInterface.ORIENTATION_ROTATE_180:
return false;
case ExifInterface.ORIENTATION_FLIP_VERTICAL:
return false;
case ExifInterface.ORIENTATION_TRANSPOSE:
return false;
case ExifInterface.ORIENTATION_ROTATE_90:
return false;
case ExifInterface.ORIENTATION_TRANSVERSE:
return false;
case ExifInterface.ORIENTATION_ROTATE_270:
return false;
case ExifInterface.ORIENTATION_NORMAL:
// Fall-through
case ExifInterface.ORIENTATION_UNDEFINED:
// Fall-through
default:
return false;
}
}
private void attachLastModifiedTimestamp() {
long now = System.currentTimeMillis();
String datetime = convertToExifDateTime(now);
mExifInterface.setAttribute(ExifInterface.TAG_DATETIME, datetime);
try {
String subsec = Long.toString(now - convertFromExifDateTime(datetime).getTime());
mExifInterface.setAttribute(ExifInterface.TAG_SUBSEC_TIME, subsec);
} catch (ParseException e) {
}
}
/**
* @return The timestamp (in millis) that this picture was modified, or {@link
* #INVALID_TIMESTAMP} if no time is available.
*/
public long getLastModifiedTimestamp() {
long timestamp = parseTimestamp(mExifInterface.getAttribute(ExifInterface.TAG_DATETIME));
if (timestamp == INVALID_TIMESTAMP) {
return INVALID_TIMESTAMP;
}
String subSecs = mExifInterface.getAttribute(ExifInterface.TAG_SUBSEC_TIME);
if (subSecs != null) {
try {
long sub = Long.parseLong(subSecs);
while (sub > 1000) {
sub /= 10;
}
timestamp += sub;
} catch (NumberFormatException e) {
// Ignored
}
}
return timestamp;
}
/**
* @return The timestamp (in millis) that this picture was taken, or {@link #INVALID_TIMESTAMP}
* if no time is available.
*/
public long getTimestamp() {
long timestamp =
parseTimestamp(mExifInterface.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL));
if (timestamp == INVALID_TIMESTAMP) {
return INVALID_TIMESTAMP;
}
String subSecs = mExifInterface.getAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL);
if (subSecs != null) {
try {
long sub = Long.parseLong(subSecs);
while (sub > 1000) {
sub /= 10;
}
timestamp += sub;
} catch (NumberFormatException e) {
// Ignored
}
}
return timestamp;
}
/** @return The location this picture was taken, or null if no location is available. */
@Nullable
public Location getLocation() {
String provider = mExifInterface.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD);
double[] latlng = mExifInterface.getLatLong();
double altitude = mExifInterface.getAltitude(0);
double speed = mExifInterface.getAttributeDouble(ExifInterface.TAG_GPS_SPEED, 0);
String speedRef = mExifInterface.getAttribute(ExifInterface.TAG_GPS_SPEED_REF);
speedRef = speedRef == null ? KILOMETERS_PER_HOUR : speedRef; // Ensure speedRef is not null
long timestamp =
parseTimestamp(
mExifInterface.getAttribute(ExifInterface.TAG_GPS_DATESTAMP),
mExifInterface.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP));
if (latlng == null) {
return null;
}
if (provider == null) {
provider = TAG;
}
Location location = new Location(provider);
location.setLatitude(latlng[0]);
location.setLongitude(latlng[1]);
if (altitude != 0) {
location.setAltitude(altitude);
}
if (speed != 0) {
switch (speedRef) {
case MILES_PER_HOUR:
speed = Speed.fromMilesPerHour(speed).toMetersPerSecond();
break;
case KNOTS:
speed = Speed.fromKnots(speed).toMetersPerSecond();
break;
case KILOMETERS_PER_HOUR:
// fall through
default:
speed = Speed.fromKilometersPerHour(speed).toMetersPerSecond();
break;
}
location.setSpeed((float) speed);
}
if (timestamp != INVALID_TIMESTAMP) {
location.setTime(timestamp);
}
return location;
}
/**
* Rotates the image by the given degrees. Can only rotate by right angles (eg. 90, 180, -90).
* Other increments will set the orientation to undefined.
*/
public void rotate(int degrees) {
if (degrees % 90 != 0) {
Logger.w(
TAG,
String.format(Locale.US,
"Can only rotate in right angles (eg. 0, 90, 180, 270). %d is "
+ "unsupported.",
degrees));
mExifInterface.setAttribute(
ExifInterface.TAG_ORIENTATION,
String.valueOf(ExifInterface.ORIENTATION_UNDEFINED));
return;
}
degrees %= 360;
int orientation = getOrientation();
while (degrees < 0) {
degrees += 90;
switch (orientation) {
case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
orientation = ExifInterface.ORIENTATION_TRANSPOSE;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
orientation = ExifInterface.ORIENTATION_ROTATE_90;
break;
case ExifInterface.ORIENTATION_FLIP_VERTICAL:
orientation = ExifInterface.ORIENTATION_TRANSVERSE;
break;
case ExifInterface.ORIENTATION_TRANSPOSE:
orientation = ExifInterface.ORIENTATION_FLIP_VERTICAL;
break;
case ExifInterface.ORIENTATION_ROTATE_90:
orientation = ExifInterface.ORIENTATION_NORMAL;
break;
case ExifInterface.ORIENTATION_TRANSVERSE:
orientation = ExifInterface.ORIENTATION_FLIP_HORIZONTAL;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
orientation = ExifInterface.ORIENTATION_ROTATE_90;
break;
case ExifInterface.ORIENTATION_NORMAL:
// Fall-through
case ExifInterface.ORIENTATION_UNDEFINED:
// Fall-through
default:
orientation = ExifInterface.ORIENTATION_ROTATE_270;
break;
}
}
while (degrees > 0) {
degrees -= 90;
switch (orientation) {
case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
orientation = ExifInterface.ORIENTATION_TRANSVERSE;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
orientation = ExifInterface.ORIENTATION_ROTATE_270;
break;
case ExifInterface.ORIENTATION_FLIP_VERTICAL:
orientation = ExifInterface.ORIENTATION_TRANSPOSE;
break;
case ExifInterface.ORIENTATION_TRANSPOSE:
orientation = ExifInterface.ORIENTATION_FLIP_HORIZONTAL;
break;
case ExifInterface.ORIENTATION_ROTATE_90:
orientation = ExifInterface.ORIENTATION_ROTATE_180;
break;
case ExifInterface.ORIENTATION_TRANSVERSE:
orientation = ExifInterface.ORIENTATION_FLIP_VERTICAL;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
orientation = ExifInterface.ORIENTATION_NORMAL;
break;
case ExifInterface.ORIENTATION_NORMAL:
// Fall-through
case ExifInterface.ORIENTATION_UNDEFINED:
// Fall-through
default:
orientation = ExifInterface.ORIENTATION_ROTATE_90;
break;
}
}
mExifInterface.setAttribute(ExifInterface.TAG_ORIENTATION, String.valueOf(orientation));
}
/**
* Sets attributes to represent a flip of the image over the horizon so that the top and bottom
* are reversed.
*/
public void flipVertically() {
int orientation;
switch (getOrientation()) {
case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
orientation = ExifInterface.ORIENTATION_ROTATE_180;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
orientation = ExifInterface.ORIENTATION_FLIP_HORIZONTAL;
break;
case ExifInterface.ORIENTATION_FLIP_VERTICAL:
orientation = ExifInterface.ORIENTATION_NORMAL;
break;
case ExifInterface.ORIENTATION_TRANSPOSE:
orientation = ExifInterface.ORIENTATION_ROTATE_270;
break;
case ExifInterface.ORIENTATION_ROTATE_90:
orientation = ExifInterface.ORIENTATION_TRANSVERSE;
break;
case ExifInterface.ORIENTATION_TRANSVERSE:
orientation = ExifInterface.ORIENTATION_ROTATE_90;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
orientation = ExifInterface.ORIENTATION_TRANSPOSE;
break;
case ExifInterface.ORIENTATION_NORMAL:
// Fall-through
case ExifInterface.ORIENTATION_UNDEFINED:
// Fall-through
default:
orientation = ExifInterface.ORIENTATION_FLIP_VERTICAL;
break;
}
mExifInterface.setAttribute(ExifInterface.TAG_ORIENTATION, String.valueOf(orientation));
}
/**
* Sets attributes to represent a flip of the image over the vertical so that the left and right
* are reversed.
*/
public void flipHorizontally() {
int orientation;
switch (getOrientation()) {
case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
orientation = ExifInterface.ORIENTATION_NORMAL;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
orientation = ExifInterface.ORIENTATION_FLIP_VERTICAL;
break;
case ExifInterface.ORIENTATION_FLIP_VERTICAL:
orientation = ExifInterface.ORIENTATION_ROTATE_180;
break;
case ExifInterface.ORIENTATION_TRANSPOSE:
orientation = ExifInterface.ORIENTATION_ROTATE_90;
break;
case ExifInterface.ORIENTATION_ROTATE_90:
orientation = ExifInterface.ORIENTATION_TRANSPOSE;
break;
case ExifInterface.ORIENTATION_TRANSVERSE:
orientation = ExifInterface.ORIENTATION_ROTATE_270;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
orientation = ExifInterface.ORIENTATION_TRANSVERSE;
break;
case ExifInterface.ORIENTATION_NORMAL:
// Fall-through
case ExifInterface.ORIENTATION_UNDEFINED:
// Fall-through
default:
orientation = ExifInterface.ORIENTATION_FLIP_HORIZONTAL;
break;
}
mExifInterface.setAttribute(ExifInterface.TAG_ORIENTATION, String.valueOf(orientation));
}
@VisibleForTesting
@Nullable
public String getMetadata() {
return mExifInterface.getAttribute(ExifInterface.TAG_XMP);
}
@VisibleForTesting
@NonNull
public ExifInterface getExifInterface() {
return mExifInterface;
}
/** Attaches the current timestamp to the file. */
public void attachTimestamp() {
long now = System.currentTimeMillis();
String datetime = convertToExifDateTime(now);
mExifInterface.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, datetime);
mExifInterface.setAttribute(ExifInterface.TAG_DATETIME_DIGITIZED, datetime);
try {
String subsec = Long.toString(now - convertFromExifDateTime(datetime).getTime());
mExifInterface.setAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, subsec);
mExifInterface.setAttribute(ExifInterface.TAG_SUBSEC_TIME_DIGITIZED, subsec);
} catch (ParseException e) {
}
mRemoveTimestamp = false;
}
/** Removes the timestamp from the file. */
public void removeTimestamp() {
mExifInterface.setAttribute(ExifInterface.TAG_DATETIME, null);
mExifInterface.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, null);
mExifInterface.setAttribute(ExifInterface.TAG_DATETIME_DIGITIZED, null);
mExifInterface.setAttribute(ExifInterface.TAG_SUBSEC_TIME, null);
mExifInterface.setAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, null);
mExifInterface.setAttribute(ExifInterface.TAG_SUBSEC_TIME_DIGITIZED, null);
mRemoveTimestamp = true;
}
/** Attaches the given location to the file. */
public void attachLocation(@NonNull Location location) {
mExifInterface.setGpsInfo(location);
}
/** Removes the location from the file. */
public void removeLocation() {
mExifInterface.setAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD, null);
mExifInterface.setAttribute(ExifInterface.TAG_GPS_LATITUDE, null);
mExifInterface.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, null);
mExifInterface.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, null);
mExifInterface.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, null);
mExifInterface.setAttribute(ExifInterface.TAG_GPS_ALTITUDE, null);
mExifInterface.setAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF, null);
mExifInterface.setAttribute(ExifInterface.TAG_GPS_SPEED, null);
mExifInterface.setAttribute(ExifInterface.TAG_GPS_SPEED_REF, null);
mExifInterface.setAttribute(ExifInterface.TAG_GPS_DATESTAMP, null);
mExifInterface.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, null);
}
/** @return The timestamp (in millis), or {@link #INVALID_TIMESTAMP} if no time is available. */
private long parseTimestamp(@Nullable String date, @Nullable String time) {
if (date == null && time == null) {
return INVALID_TIMESTAMP;
}
if (time == null) {
try {
return convertFromExifDate(date).getTime();
} catch (ParseException e) {
return INVALID_TIMESTAMP;
}
}
if (date == null) {
try {
return convertFromExifTime(time).getTime();
} catch (ParseException e) {
return INVALID_TIMESTAMP;
}
}
return parseTimestamp(date + " " + time);
}
/** @return The timestamp (in millis), or {@link #INVALID_TIMESTAMP} if no time is available. */
private long parseTimestamp(@Nullable String datetime) {
if (datetime == null) {
return INVALID_TIMESTAMP;
}
try {
return convertFromExifDateTime(datetime).getTime();
} catch (ParseException e) {
return INVALID_TIMESTAMP;
}
}
private static final class Speed {
static Converter fromKilometersPerHour(double kph) {
return new Converter(kph * 0.621371);
}
static Converter fromMilesPerHour(double mph) {
return new Converter(mph);
}
static Converter fromKnots(double knots) {
return new Converter(knots * 1.15078);
}
static final class Converter {
final double mMph;
Converter(double mph) {
mMph = mph;
}
double toMetersPerSecond() {
return mMph / 2.23694;
}
}
}
/**
* Creates a list that contains all public tags defined in {@link ExifInterface}.
*
* <p> Deprecated tags are not included.
*/
@NonNull
public static List<String> getAllExifTags() {
return Arrays.asList(
ExifInterface.TAG_IMAGE_WIDTH,
ExifInterface.TAG_IMAGE_LENGTH,
ExifInterface.TAG_BITS_PER_SAMPLE,
ExifInterface.TAG_COMPRESSION,
ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION,
ExifInterface.TAG_ORIENTATION,
ExifInterface.TAG_SAMPLES_PER_PIXEL,
ExifInterface.TAG_PLANAR_CONFIGURATION,
ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING,
ExifInterface.TAG_Y_CB_CR_POSITIONING,
ExifInterface.TAG_X_RESOLUTION,
ExifInterface.TAG_Y_RESOLUTION,
ExifInterface.TAG_RESOLUTION_UNIT,
ExifInterface.TAG_STRIP_OFFSETS,
ExifInterface.TAG_ROWS_PER_STRIP,
ExifInterface.TAG_STRIP_BYTE_COUNTS,
ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT,
ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
ExifInterface.TAG_TRANSFER_FUNCTION,
ExifInterface.TAG_WHITE_POINT,
ExifInterface.TAG_PRIMARY_CHROMATICITIES,
ExifInterface.TAG_Y_CB_CR_COEFFICIENTS,
ExifInterface.TAG_REFERENCE_BLACK_WHITE,
ExifInterface.TAG_DATETIME,
ExifInterface.TAG_IMAGE_DESCRIPTION,
ExifInterface.TAG_MAKE,
ExifInterface.TAG_MODEL,
ExifInterface.TAG_SOFTWARE,
ExifInterface.TAG_ARTIST,
ExifInterface.TAG_COPYRIGHT,
ExifInterface.TAG_EXIF_VERSION,
ExifInterface.TAG_FLASHPIX_VERSION,
ExifInterface.TAG_COLOR_SPACE,
ExifInterface.TAG_GAMMA,
ExifInterface.TAG_PIXEL_X_DIMENSION,
ExifInterface.TAG_PIXEL_Y_DIMENSION,
ExifInterface.TAG_COMPONENTS_CONFIGURATION,
ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL,
ExifInterface.TAG_MAKER_NOTE,
ExifInterface.TAG_USER_COMMENT,
ExifInterface.TAG_RELATED_SOUND_FILE,
ExifInterface.TAG_DATETIME_ORIGINAL,
ExifInterface.TAG_DATETIME_DIGITIZED,
ExifInterface.TAG_OFFSET_TIME,
ExifInterface.TAG_OFFSET_TIME_ORIGINAL,
ExifInterface.TAG_OFFSET_TIME_DIGITIZED,
ExifInterface.TAG_SUBSEC_TIME,
ExifInterface.TAG_SUBSEC_TIME_ORIGINAL,
ExifInterface.TAG_SUBSEC_TIME_DIGITIZED,
ExifInterface.TAG_EXPOSURE_TIME,
ExifInterface.TAG_F_NUMBER,
ExifInterface.TAG_EXPOSURE_PROGRAM,
ExifInterface.TAG_SPECTRAL_SENSITIVITY,
ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY,
ExifInterface.TAG_OECF,
ExifInterface.TAG_SENSITIVITY_TYPE,
ExifInterface.TAG_STANDARD_OUTPUT_SENSITIVITY,
ExifInterface.TAG_RECOMMENDED_EXPOSURE_INDEX,
ExifInterface.TAG_ISO_SPEED,
ExifInterface.TAG_ISO_SPEED_LATITUDE_YYY,
ExifInterface.TAG_ISO_SPEED_LATITUDE_ZZZ,
ExifInterface.TAG_SHUTTER_SPEED_VALUE,
ExifInterface.TAG_APERTURE_VALUE,
ExifInterface.TAG_BRIGHTNESS_VALUE,
ExifInterface.TAG_EXPOSURE_BIAS_VALUE,
ExifInterface.TAG_MAX_APERTURE_VALUE,
ExifInterface.TAG_SUBJECT_DISTANCE,
ExifInterface.TAG_METERING_MODE,
ExifInterface.TAG_LIGHT_SOURCE,
ExifInterface.TAG_FLASH,
ExifInterface.TAG_SUBJECT_AREA,
ExifInterface.TAG_FOCAL_LENGTH,
ExifInterface.TAG_FLASH_ENERGY,
ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE,
ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION,
ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION,
ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT,
ExifInterface.TAG_SUBJECT_LOCATION,
ExifInterface.TAG_EXPOSURE_INDEX,
ExifInterface.TAG_SENSING_METHOD,
ExifInterface.TAG_FILE_SOURCE,
ExifInterface.TAG_SCENE_TYPE,
ExifInterface.TAG_CFA_PATTERN,
ExifInterface.TAG_CUSTOM_RENDERED,
ExifInterface.TAG_EXPOSURE_MODE,
ExifInterface.TAG_WHITE_BALANCE,
ExifInterface.TAG_DIGITAL_ZOOM_RATIO,
ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM,
ExifInterface.TAG_SCENE_CAPTURE_TYPE,
ExifInterface.TAG_GAIN_CONTROL,
ExifInterface.TAG_CONTRAST,
ExifInterface.TAG_SATURATION,
ExifInterface.TAG_SHARPNESS,
ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION,
ExifInterface.TAG_SUBJECT_DISTANCE_RANGE,
ExifInterface.TAG_IMAGE_UNIQUE_ID,
ExifInterface.TAG_CAMERA_OWNER_NAME,
ExifInterface.TAG_BODY_SERIAL_NUMBER,
ExifInterface.TAG_LENS_SPECIFICATION,
ExifInterface.TAG_LENS_MAKE,
ExifInterface.TAG_LENS_MODEL,
ExifInterface.TAG_LENS_SERIAL_NUMBER,
ExifInterface.TAG_GPS_VERSION_ID,
ExifInterface.TAG_GPS_LATITUDE_REF,
ExifInterface.TAG_GPS_LATITUDE,
ExifInterface.TAG_GPS_LONGITUDE_REF,
ExifInterface.TAG_GPS_LONGITUDE,
ExifInterface.TAG_GPS_ALTITUDE_REF,
ExifInterface.TAG_GPS_ALTITUDE,
ExifInterface.TAG_GPS_TIMESTAMP,
ExifInterface.TAG_GPS_SATELLITES,
ExifInterface.TAG_GPS_STATUS,
ExifInterface.TAG_GPS_MEASURE_MODE,
ExifInterface.TAG_GPS_DOP,
ExifInterface.TAG_GPS_SPEED_REF,
ExifInterface.TAG_GPS_SPEED,
ExifInterface.TAG_GPS_TRACK_REF,
ExifInterface.TAG_GPS_TRACK,
ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
ExifInterface.TAG_GPS_IMG_DIRECTION,
ExifInterface.TAG_GPS_MAP_DATUM,
ExifInterface.TAG_GPS_DEST_LATITUDE_REF,
ExifInterface.TAG_GPS_DEST_LATITUDE,
ExifInterface.TAG_GPS_DEST_LONGITUDE_REF,
ExifInterface.TAG_GPS_DEST_LONGITUDE,
ExifInterface.TAG_GPS_DEST_BEARING_REF,
ExifInterface.TAG_GPS_DEST_BEARING,
ExifInterface.TAG_GPS_DEST_DISTANCE_REF,
ExifInterface.TAG_GPS_DEST_DISTANCE,
ExifInterface.TAG_GPS_PROCESSING_METHOD,
ExifInterface.TAG_GPS_AREA_INFORMATION,
ExifInterface.TAG_GPS_DATESTAMP,
ExifInterface.TAG_GPS_DIFFERENTIAL,
ExifInterface.TAG_GPS_H_POSITIONING_ERROR,
ExifInterface.TAG_INTEROPERABILITY_INDEX,
ExifInterface.TAG_THUMBNAIL_IMAGE_LENGTH,
ExifInterface.TAG_THUMBNAIL_IMAGE_WIDTH,
TAG_THUMBNAIL_ORIENTATION,
ExifInterface.TAG_DNG_VERSION,
ExifInterface.TAG_DEFAULT_CROP_SIZE,
ExifInterface.TAG_ORF_THUMBNAIL_IMAGE,
ExifInterface.TAG_ORF_PREVIEW_IMAGE_START,
ExifInterface.TAG_ORF_PREVIEW_IMAGE_LENGTH,
ExifInterface.TAG_ORF_ASPECT_FRAME,
ExifInterface.TAG_RW2_SENSOR_BOTTOM_BORDER,
ExifInterface.TAG_RW2_SENSOR_LEFT_BORDER,
ExifInterface.TAG_RW2_SENSOR_RIGHT_BORDER,
ExifInterface.TAG_RW2_SENSOR_TOP_BORDER,
ExifInterface.TAG_RW2_ISO,
ExifInterface.TAG_RW2_JPG_FROM_RAW,
ExifInterface.TAG_XMP,
ExifInterface.TAG_NEW_SUBFILE_TYPE,
ExifInterface.TAG_SUBFILE_TYPE);
}
}