Files
frameworks_base/media/jni/android_media_ExifInterface.cpp
Leon Scroggins III adb5e0ba6d Stop explicitly using kCallerPasses_Ownership
Bug: 65646012
Bug: 65426286

Cherry-picked from master. The original CL just updates BitmapFactory.
This also updates ExifInterface in the same way. ExifInterface.cpp no
longer existed by the time the Skia change landed.

Required for Ib7aaf367f68f8764147887d858f65ee14fa7a1d9, which fixes
the aforementioned bugs.

Test: No change in behavior, no new tests.

This enum is going away in https://skia-review.googlesource.com/c/9498,
and this method is using the default anyway.

Merged-In: I928a72851f90e84f45ce2fa49560f8ba894a8058
Merged-In: I8f6c761e50cb29476dc0784f58908ec35e3c70e9
Change-Id: I928a72851f90e84f45ce2fa49560f8ba894a8058
2017-09-14 20:10:22 +00:00

427 lines
15 KiB
C++

/*
* Copyright 2016 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.
*/
//#define LOG_NDEBUG 0
#define LOG_TAG "ExifInterface_JNI"
#include "android_media_Utils.h"
#include "android/graphics/CreateJavaOutputStreamAdaptor.h"
#include "src/piex_types.h"
#include "src/piex.h"
#include <jni.h>
#include <JNIHelp.h>
#include <androidfw/Asset.h>
#include <android_runtime/AndroidRuntime.h>
#include <android/graphics/Utils.h>
#include <nativehelper/ScopedLocalRef.h>
#include <utils/Log.h>
#include <utils/String8.h>
#include <utils/KeyedVector.h>
// ----------------------------------------------------------------------------
using namespace android;
static const char kJpegSignatureChars[] = {(char)0xff, (char)0xd8, (char)0xff};
static const int kJpegSignatureSize = 3;
#define FIND_CLASS(var, className) \
var = env->FindClass(className); \
LOG_FATAL_IF(! var, "Unable to find class " className);
#define GET_METHOD_ID(var, clazz, fieldName, fieldDescriptor) \
var = env->GetMethodID(clazz, fieldName, fieldDescriptor); \
LOG_FATAL_IF(! var, "Unable to find method " fieldName);
struct HashMapFields {
jmethodID init;
jmethodID put;
};
struct fields_t {
HashMapFields hashMap;
jclass hashMapClassId;
};
static fields_t gFields;
static jobject KeyedVectorToHashMap(JNIEnv *env, KeyedVector<String8, String8> const &map) {
jclass clazz = gFields.hashMapClassId;
jobject hashMap = env->NewObject(clazz, gFields.hashMap.init);
for (size_t i = 0; i < map.size(); ++i) {
jstring jkey = env->NewStringUTF(map.keyAt(i).string());
jstring jvalue = env->NewStringUTF(map.valueAt(i).string());
env->CallObjectMethod(hashMap, gFields.hashMap.put, jkey, jvalue);
env->DeleteLocalRef(jkey);
env->DeleteLocalRef(jvalue);
}
return hashMap;
}
extern "C" {
// -------------------------- ExifInterface methods ---------------------------
static void ExifInterface_initRaw(JNIEnv *env) {
jclass clazz;
FIND_CLASS(clazz, "java/util/HashMap");
gFields.hashMapClassId = static_cast<jclass>(env->NewGlobalRef(clazz));
GET_METHOD_ID(gFields.hashMap.init, clazz, "<init>", "()V");
GET_METHOD_ID(gFields.hashMap.put, clazz, "put",
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
}
static bool is_asset_stream(const SkStream& stream) {
return stream.hasLength() && stream.hasPosition();
}
static jobject ExifInterface_getThumbnailFromAsset(
JNIEnv* env, jclass /* clazz */, jlong jasset, jint jthumbnailOffset,
jint jthumbnailLength) {
Asset* asset = reinterpret_cast<Asset*>(jasset);
std::unique_ptr<AssetStreamAdaptor> stream(new AssetStreamAdaptor(asset));
std::unique_ptr<jbyte[]> thumbnailData(new jbyte[(int)jthumbnailLength]);
if (thumbnailData.get() == NULL) {
ALOGI("No memory to get thumbnail");
return NULL;
}
// Do not know the current offset. So rewind it.
stream->rewind();
// Read thumbnail.
stream->skip((int)jthumbnailOffset);
stream->read((void*)thumbnailData.get(), (int)jthumbnailLength);
// Copy to the byte array.
jbyteArray byteArray = env->NewByteArray(jthumbnailLength);
env->SetByteArrayRegion(byteArray, 0, jthumbnailLength, thumbnailData.get());
return byteArray;
}
static jobject getRawAttributes(JNIEnv* env, SkStream* stream, bool returnThumbnail) {
std::unique_ptr<SkStream> streamDeleter(stream);
std::unique_ptr<::piex::StreamInterface> piexStream;
if (is_asset_stream(*stream)) {
piexStream.reset(new AssetStream(streamDeleter.release()));
} else {
piexStream.reset(new BufferedStream(streamDeleter.release()));
}
piex::PreviewImageData image_data;
if (!GetExifFromRawImage(piexStream.get(), String8("[piex stream]"), image_data)) {
ALOGI("Raw image not detected");
return NULL;
}
KeyedVector<String8, String8> map;
if (image_data.thumbnail.length > 0
&& image_data.thumbnail.format == ::piex::Image::kJpegCompressed) {
map.add(String8("HasThumbnail"), String8("true"));
map.add(String8("ThumbnailOffset"), String8::format("%d", image_data.thumbnail.offset));
map.add(String8("ThumbnailLength"), String8::format("%d", image_data.thumbnail.length));
} else {
map.add(String8("HasThumbnail"), String8("false"));
}
map.add(
String8("Orientation"),
String8::format("%u", image_data.exif_orientation));
map.add(
String8("ImageWidth"),
String8::format("%u", image_data.full_width));
map.add(
String8("ImageLength"),
String8::format("%u", image_data.full_height));
// Current PIEX does not have LightSource information while JPEG version of
// EXIFInterface always declares the light source field. For the
// compatibility, it provides the default value of the light source field.
map.add(String8("LightSource"), String8("0"));
if (!image_data.maker.empty()) {
map.add(String8("Make"), String8(image_data.maker.c_str()));
}
if (!image_data.model.empty()) {
map.add(String8("Model"), String8(image_data.model.c_str()));
}
if (!image_data.date_time.empty()) {
map.add(String8("DateTime"), String8(image_data.date_time.c_str()));
}
if (image_data.iso) {
map.add(
String8("ISOSpeedRatings"),
String8::format("%u", image_data.iso));
}
if (image_data.exposure_time.numerator != 0
&& image_data.exposure_time.denominator != 0) {
double exposureTime =
(double)image_data.exposure_time.numerator
/ image_data.exposure_time.denominator;
const char* format;
if (exposureTime < 0.01) {
format = "%6.4f";
} else {
format = "%5.3f";
}
map.add(String8("ExposureTime"), String8::format(format, exposureTime));
}
if (image_data.fnumber.numerator != 0
&& image_data.fnumber.denominator != 0) {
double fnumber =
(double)image_data.fnumber.numerator
/ image_data.fnumber.denominator;
map.add(String8("FNumber"), String8::format("%5.3f", fnumber));
}
if (image_data.focal_length.numerator != 0
&& image_data.focal_length.denominator != 0) {
map.add(
String8("FocalLength"),
String8::format(
"%u/%u",
image_data.focal_length.numerator,
image_data.focal_length.denominator));
}
if (image_data.gps.is_valid) {
if (image_data.gps.latitude[0].denominator != 0
&& image_data.gps.latitude[1].denominator != 0
&& image_data.gps.latitude[2].denominator != 0) {
map.add(
String8("GPSLatitude"),
String8::format(
"%u/%u,%u/%u,%u/%u",
image_data.gps.latitude[0].numerator,
image_data.gps.latitude[0].denominator,
image_data.gps.latitude[1].numerator,
image_data.gps.latitude[1].denominator,
image_data.gps.latitude[2].numerator,
image_data.gps.latitude[2].denominator));
}
if (image_data.gps.latitude_ref) {
char str[2];
str[0] = image_data.gps.latitude_ref;
str[1] = 0;
map.add(String8("GPSLatitudeRef"), String8(str));
}
if (image_data.gps.longitude[0].denominator != 0
&& image_data.gps.longitude[1].denominator != 0
&& image_data.gps.longitude[2].denominator != 0) {
map.add(
String8("GPSLongitude"),
String8::format(
"%u/%u,%u/%u,%u/%u",
image_data.gps.longitude[0].numerator,
image_data.gps.longitude[0].denominator,
image_data.gps.longitude[1].numerator,
image_data.gps.longitude[1].denominator,
image_data.gps.longitude[2].numerator,
image_data.gps.longitude[2].denominator));
}
if (image_data.gps.longitude_ref) {
char str[2];
str[0] = image_data.gps.longitude_ref;
str[1] = 0;
map.add(String8("GPSLongitudeRef"), String8(str));
}
if (image_data.gps.altitude.denominator != 0) {
map.add(
String8("GPSAltitude"),
String8::format("%u/%u",
image_data.gps.altitude.numerator,
image_data.gps.altitude.denominator));
map.add(
String8("GPSAltitudeRef"),
String8(image_data.gps.altitude_ref ? "1" : "0"));
}
if (image_data.gps.time_stamp[0].denominator != 0
&& image_data.gps.time_stamp[1].denominator != 0
&& image_data.gps.time_stamp[2].denominator != 0) {
map.add(
String8("GPSTimeStamp"),
String8::format(
"%02u:%02u:%02u",
image_data.gps.time_stamp[0].numerator
/ image_data.gps.time_stamp[0].denominator,
image_data.gps.time_stamp[1].numerator
/ image_data.gps.time_stamp[1].denominator,
image_data.gps.time_stamp[2].numerator
/ image_data.gps.time_stamp[2].denominator));
}
if (!image_data.gps.date_stamp.empty()) {
map.add(
String8("GPSDateStamp"),
String8(image_data.gps.date_stamp.c_str()));
}
}
jobject hashMap = KeyedVectorToHashMap(env, map);
if (returnThumbnail) {
std::unique_ptr<jbyte[]> thumbnailData(new jbyte[image_data.thumbnail.length]);
if (thumbnailData.get() == NULL) {
ALOGE("No memory to parse a thumbnail");
return NULL;
}
jbyteArray jthumbnailByteArray = env->NewByteArray(image_data.thumbnail.length);
if (jthumbnailByteArray == NULL) {
ALOGE("No memory to parse a thumbnail");
return NULL;
}
piexStream.get()->GetData(image_data.thumbnail.offset, image_data.thumbnail.length,
(uint8_t*)thumbnailData.get());
env->SetByteArrayRegion(
jthumbnailByteArray, 0, image_data.thumbnail.length, thumbnailData.get());
jstring jkey = env->NewStringUTF(String8("ThumbnailData"));
env->CallObjectMethod(hashMap, gFields.hashMap.put, jkey, jthumbnailByteArray);
env->DeleteLocalRef(jkey);
env->DeleteLocalRef(jthumbnailByteArray);
}
return hashMap;
}
static jobject ExifInterface_getRawAttributesFromAsset(
JNIEnv* env, jclass /* clazz */, jlong jasset) {
std::unique_ptr<char[]> jpegSignature(new char[kJpegSignatureSize]);
if (jpegSignature.get() == NULL) {
ALOGE("No enough memory to parse");
return NULL;
}
Asset* asset = reinterpret_cast<Asset*>(jasset);
std::unique_ptr<AssetStreamAdaptor> stream(new AssetStreamAdaptor(asset));
if (stream.get()->read(jpegSignature.get(), kJpegSignatureSize) != kJpegSignatureSize) {
// Rewind the stream.
stream.get()->rewind();
ALOGI("Corrupted image.");
return NULL;
}
// Rewind the stream.
stream.get()->rewind();
if (memcmp(jpegSignature.get(), kJpegSignatureChars, kJpegSignatureSize) == 0) {
ALOGI("Should be a JPEG stream.");
return NULL;
}
// Try to parse from the given stream.
jobject result = getRawAttributes(env, stream.get(), false);
// Rewind the stream for the chance to read JPEG.
if (result == NULL) {
stream.get()->rewind();
}
return result;
}
static jobject ExifInterface_getRawAttributesFromFileDescriptor(
JNIEnv* env, jclass /* clazz */, jobject jfileDescriptor) {
std::unique_ptr<char[]> jpegSignature(new char[kJpegSignatureSize]);
if (jpegSignature.get() == NULL) {
ALOGE("No enough memory to parse");
return NULL;
}
int fd = jniGetFDFromFileDescriptor(env, jfileDescriptor);
if (fd < 0) {
ALOGI("Invalid file descriptor");
return NULL;
}
// Restore the file descriptor's offset on exiting this function.
AutoFDSeek autoRestore(fd);
int dupFd = dup(fd);
FILE* file = fdopen(dupFd, "r");
if (file == NULL) {
ALOGI("Failed to open the file descriptor");
return NULL;
}
if (fgets(jpegSignature.get(), kJpegSignatureSize, file) == NULL) {
ALOGI("Corrupted image.");
return NULL;
}
if (memcmp(jpegSignature.get(), kJpegSignatureChars, kJpegSignatureSize) == 0) {
ALOGI("Should be a JPEG stream.");
return NULL;
}
// Rewind the file descriptor.
fseek(file, 0L, SEEK_SET);
std::unique_ptr<SkFILEStream> fileStream(new SkFILEStream(file));
return getRawAttributes(env, fileStream.get(), false);
}
static jobject ExifInterface_getRawAttributesFromInputStream(
JNIEnv* env, jclass /* clazz */, jobject jinputStream) {
jbyteArray byteArray = env->NewByteArray(8*1024);
ScopedLocalRef<jbyteArray> scoper(env, byteArray);
std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, jinputStream, scoper.get()));
return getRawAttributes(env, stream.get(), true);
}
} // extern "C"
// ----------------------------------------------------------------------------
static JNINativeMethod gMethods[] = {
{ "nativeInitRaw", "()V", (void *)ExifInterface_initRaw },
{ "nativeGetThumbnailFromAsset", "(JII)[B", (void *)ExifInterface_getThumbnailFromAsset },
{ "nativeGetRawAttributesFromAsset", "(J)Ljava/util/HashMap;",
(void*)ExifInterface_getRawAttributesFromAsset },
{ "nativeGetRawAttributesFromFileDescriptor", "(Ljava/io/FileDescriptor;)Ljava/util/HashMap;",
(void*)ExifInterface_getRawAttributesFromFileDescriptor },
{ "nativeGetRawAttributesFromInputStream", "(Ljava/io/InputStream;)Ljava/util/HashMap;",
(void*)ExifInterface_getRawAttributesFromInputStream },
};
int register_android_media_ExifInterface(JNIEnv *env) {
return AndroidRuntime::registerNativeMethods(
env,
"android/media/ExifInterface",
gMethods,
NELEM(gMethods));
}