1/* 2 * Copyright 2016 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//#define LOG_NDEBUG 0 18#define LOG_TAG "ExifInterface_JNI" 19 20#include "android_media_Utils.h" 21 22#include "android/graphics/CreateJavaOutputStreamAdaptor.h" 23#include "src/piex_types.h" 24#include "src/piex.h" 25 26#include <jni.h> 27#include <JNIHelp.h> 28#include <androidfw/Asset.h> 29#include <android_runtime/AndroidRuntime.h> 30#include <android/graphics/Utils.h> 31#include <nativehelper/ScopedLocalRef.h> 32 33#include <utils/Log.h> 34#include <utils/String8.h> 35#include <utils/KeyedVector.h> 36 37// ---------------------------------------------------------------------------- 38 39using namespace android; 40 41static const char kJpegSignatureChars[] = {(char)0xff, (char)0xd8, (char)0xff}; 42static const int kJpegSignatureSize = 3; 43 44#define FIND_CLASS(var, className) \ 45 var = env->FindClass(className); \ 46 LOG_FATAL_IF(! var, "Unable to find class " className); 47 48#define GET_METHOD_ID(var, clazz, fieldName, fieldDescriptor) \ 49 var = env->GetMethodID(clazz, fieldName, fieldDescriptor); \ 50 LOG_FATAL_IF(! var, "Unable to find method " fieldName); 51 52struct HashMapFields { 53 jmethodID init; 54 jmethodID put; 55}; 56 57struct fields_t { 58 HashMapFields hashMap; 59 jclass hashMapClassId; 60}; 61 62static fields_t gFields; 63 64static jobject KeyedVectorToHashMap(JNIEnv *env, KeyedVector<String8, String8> const &map) { 65 jclass clazz = gFields.hashMapClassId; 66 jobject hashMap = env->NewObject(clazz, gFields.hashMap.init); 67 for (size_t i = 0; i < map.size(); ++i) { 68 jstring jkey = env->NewStringUTF(map.keyAt(i).string()); 69 jstring jvalue = env->NewStringUTF(map.valueAt(i).string()); 70 env->CallObjectMethod(hashMap, gFields.hashMap.put, jkey, jvalue); 71 env->DeleteLocalRef(jkey); 72 env->DeleteLocalRef(jvalue); 73 } 74 return hashMap; 75} 76 77extern "C" { 78 79// -------------------------- ExifInterface methods --------------------------- 80 81static void ExifInterface_initRaw(JNIEnv *env) { 82 jclass clazz; 83 FIND_CLASS(clazz, "java/util/HashMap"); 84 gFields.hashMapClassId = static_cast<jclass>(env->NewGlobalRef(clazz)); 85 86 GET_METHOD_ID(gFields.hashMap.init, clazz, "<init>", "()V"); 87 GET_METHOD_ID(gFields.hashMap.put, clazz, "put", 88 "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); 89} 90 91static bool is_asset_stream(const SkStream& stream) { 92 return stream.hasLength() && stream.hasPosition(); 93} 94 95static jobject ExifInterface_getThumbnailFromAsset( 96 JNIEnv* env, jclass /* clazz */, jlong jasset, jint jthumbnailOffset, 97 jint jthumbnailLength) { 98 Asset* asset = reinterpret_cast<Asset*>(jasset); 99 std::unique_ptr<AssetStreamAdaptor> stream(new AssetStreamAdaptor(asset)); 100 101 std::unique_ptr<jbyte[]> thumbnailData(new jbyte[(int)jthumbnailLength]); 102 if (thumbnailData.get() == NULL) { 103 ALOGI("No memory to get thumbnail"); 104 return NULL; 105 } 106 107 // Do not know the current offset. So rewind it. 108 stream->rewind(); 109 110 // Read thumbnail. 111 stream->skip((int)jthumbnailOffset); 112 stream->read((void*)thumbnailData.get(), (int)jthumbnailLength); 113 114 // Copy to the byte array. 115 jbyteArray byteArray = env->NewByteArray(jthumbnailLength); 116 env->SetByteArrayRegion(byteArray, 0, jthumbnailLength, thumbnailData.get()); 117 return byteArray; 118} 119 120static jobject getRawAttributes(JNIEnv* env, SkStream* stream, bool returnThumbnail) { 121 std::unique_ptr<SkStream> streamDeleter(stream); 122 123 std::unique_ptr<::piex::StreamInterface> piexStream; 124 if (is_asset_stream(*stream)) { 125 piexStream.reset(new AssetStream(streamDeleter.release())); 126 } else { 127 piexStream.reset(new BufferedStream(streamDeleter.release())); 128 } 129 130 piex::PreviewImageData image_data; 131 132 if (!GetExifFromRawImage(piexStream.get(), String8("[piex stream]"), image_data)) { 133 ALOGI("Raw image not detected"); 134 return NULL; 135 } 136 137 KeyedVector<String8, String8> map; 138 139 if (image_data.thumbnail.length > 0 140 && image_data.thumbnail.format == ::piex::Image::kJpegCompressed) { 141 map.add(String8("HasThumbnail"), String8("true")); 142 map.add(String8("ThumbnailOffset"), String8::format("%d", image_data.thumbnail.offset)); 143 map.add(String8("ThumbnailLength"), String8::format("%d", image_data.thumbnail.length)); 144 } else { 145 map.add(String8("HasThumbnail"), String8("false")); 146 } 147 148 map.add( 149 String8("Orientation"), 150 String8::format("%u", image_data.exif_orientation)); 151 map.add( 152 String8("ImageWidth"), 153 String8::format("%u", image_data.full_width)); 154 map.add( 155 String8("ImageLength"), 156 String8::format("%u", image_data.full_height)); 157 158 // Current PIEX does not have LightSource information while JPEG version of 159 // EXIFInterface always declares the light source field. For the 160 // compatibility, it provides the default value of the light source field. 161 map.add(String8("LightSource"), String8("0")); 162 163 if (!image_data.maker.empty()) { 164 map.add(String8("Make"), String8(image_data.maker.c_str())); 165 } 166 167 if (!image_data.model.empty()) { 168 map.add(String8("Model"), String8(image_data.model.c_str())); 169 } 170 171 if (!image_data.date_time.empty()) { 172 map.add(String8("DateTime"), String8(image_data.date_time.c_str())); 173 } 174 175 if (image_data.iso) { 176 map.add( 177 String8("ISOSpeedRatings"), 178 String8::format("%u", image_data.iso)); 179 } 180 181 if (image_data.exposure_time.numerator != 0 182 && image_data.exposure_time.denominator != 0) { 183 double exposureTime = 184 (double)image_data.exposure_time.numerator 185 / image_data.exposure_time.denominator; 186 187 const char* format; 188 if (exposureTime < 0.01) { 189 format = "%6.4f"; 190 } else { 191 format = "%5.3f"; 192 } 193 map.add(String8("ExposureTime"), String8::format(format, exposureTime)); 194 } 195 196 if (image_data.fnumber.numerator != 0 197 && image_data.fnumber.denominator != 0) { 198 double fnumber = 199 (double)image_data.fnumber.numerator 200 / image_data.fnumber.denominator; 201 map.add(String8("FNumber"), String8::format("%5.3f", fnumber)); 202 } 203 204 if (image_data.focal_length.numerator != 0 205 && image_data.focal_length.denominator != 0) { 206 map.add( 207 String8("FocalLength"), 208 String8::format( 209 "%u/%u", 210 image_data.focal_length.numerator, 211 image_data.focal_length.denominator)); 212 } 213 214 if (image_data.gps.is_valid) { 215 if (image_data.gps.latitude[0].denominator != 0 216 && image_data.gps.latitude[1].denominator != 0 217 && image_data.gps.latitude[2].denominator != 0) { 218 map.add( 219 String8("GPSLatitude"), 220 String8::format( 221 "%u/%u,%u/%u,%u/%u", 222 image_data.gps.latitude[0].numerator, 223 image_data.gps.latitude[0].denominator, 224 image_data.gps.latitude[1].numerator, 225 image_data.gps.latitude[1].denominator, 226 image_data.gps.latitude[2].numerator, 227 image_data.gps.latitude[2].denominator)); 228 } 229 230 if (image_data.gps.latitude_ref) { 231 char str[2]; 232 str[0] = image_data.gps.latitude_ref; 233 str[1] = 0; 234 map.add(String8("GPSLatitudeRef"), String8(str)); 235 } 236 237 if (image_data.gps.longitude[0].denominator != 0 238 && image_data.gps.longitude[1].denominator != 0 239 && image_data.gps.longitude[2].denominator != 0) { 240 map.add( 241 String8("GPSLongitude"), 242 String8::format( 243 "%u/%u,%u/%u,%u/%u", 244 image_data.gps.longitude[0].numerator, 245 image_data.gps.longitude[0].denominator, 246 image_data.gps.longitude[1].numerator, 247 image_data.gps.longitude[1].denominator, 248 image_data.gps.longitude[2].numerator, 249 image_data.gps.longitude[2].denominator)); 250 } 251 252 if (image_data.gps.longitude_ref) { 253 char str[2]; 254 str[0] = image_data.gps.longitude_ref; 255 str[1] = 0; 256 map.add(String8("GPSLongitudeRef"), String8(str)); 257 } 258 259 if (image_data.gps.altitude.denominator != 0) { 260 map.add( 261 String8("GPSAltitude"), 262 String8::format("%u/%u", 263 image_data.gps.altitude.numerator, 264 image_data.gps.altitude.denominator)); 265 266 map.add( 267 String8("GPSAltitudeRef"), 268 String8(image_data.gps.altitude_ref ? "1" : "0")); 269 } 270 271 if (image_data.gps.time_stamp[0].denominator != 0 272 && image_data.gps.time_stamp[1].denominator != 0 273 && image_data.gps.time_stamp[2].denominator != 0) { 274 map.add( 275 String8("GPSTimeStamp"), 276 String8::format( 277 "%02u:%02u:%02u", 278 image_data.gps.time_stamp[0].numerator 279 / image_data.gps.time_stamp[0].denominator, 280 image_data.gps.time_stamp[1].numerator 281 / image_data.gps.time_stamp[1].denominator, 282 image_data.gps.time_stamp[2].numerator 283 / image_data.gps.time_stamp[2].denominator)); 284 } 285 286 if (!image_data.gps.date_stamp.empty()) { 287 map.add( 288 String8("GPSDateStamp"), 289 String8(image_data.gps.date_stamp.c_str())); 290 } 291 } 292 293 jobject hashMap = KeyedVectorToHashMap(env, map); 294 295 if (returnThumbnail) { 296 std::unique_ptr<jbyte[]> thumbnailData(new jbyte[image_data.thumbnail.length]); 297 if (thumbnailData.get() == NULL) { 298 ALOGE("No memory to parse a thumbnail"); 299 return NULL; 300 } 301 jbyteArray jthumbnailByteArray = env->NewByteArray(image_data.thumbnail.length); 302 if (jthumbnailByteArray == NULL) { 303 ALOGE("No memory to parse a thumbnail"); 304 return NULL; 305 } 306 piexStream.get()->GetData(image_data.thumbnail.offset, image_data.thumbnail.length, 307 (uint8_t*)thumbnailData.get()); 308 env->SetByteArrayRegion( 309 jthumbnailByteArray, 0, image_data.thumbnail.length, thumbnailData.get()); 310 jstring jkey = env->NewStringUTF(String8("ThumbnailData")); 311 env->CallObjectMethod(hashMap, gFields.hashMap.put, jkey, jthumbnailByteArray); 312 env->DeleteLocalRef(jkey); 313 env->DeleteLocalRef(jthumbnailByteArray); 314 } 315 return hashMap; 316} 317 318static jobject ExifInterface_getRawAttributesFromAsset( 319 JNIEnv* env, jclass /* clazz */, jlong jasset) { 320 std::unique_ptr<char[]> jpegSignature(new char[kJpegSignatureSize]); 321 if (jpegSignature.get() == NULL) { 322 ALOGE("No enough memory to parse"); 323 return NULL; 324 } 325 326 Asset* asset = reinterpret_cast<Asset*>(jasset); 327 std::unique_ptr<AssetStreamAdaptor> stream(new AssetStreamAdaptor(asset)); 328 329 if (stream.get()->read(jpegSignature.get(), kJpegSignatureSize) != kJpegSignatureSize) { 330 // Rewind the stream. 331 stream.get()->rewind(); 332 333 ALOGI("Corrupted image."); 334 return NULL; 335 } 336 337 // Rewind the stream. 338 stream.get()->rewind(); 339 340 if (memcmp(jpegSignature.get(), kJpegSignatureChars, kJpegSignatureSize) == 0) { 341 ALOGI("Should be a JPEG stream."); 342 return NULL; 343 } 344 345 // Try to parse from the given stream. 346 jobject result = getRawAttributes(env, stream.get(), false); 347 348 // Rewind the stream for the chance to read JPEG. 349 if (result == NULL) { 350 stream.get()->rewind(); 351 } 352 return result; 353} 354 355static jobject ExifInterface_getRawAttributesFromFileDescriptor( 356 JNIEnv* env, jclass /* clazz */, jobject jfileDescriptor) { 357 std::unique_ptr<char[]> jpegSignature(new char[kJpegSignatureSize]); 358 if (jpegSignature.get() == NULL) { 359 ALOGE("No enough memory to parse"); 360 return NULL; 361 } 362 363 int fd = jniGetFDFromFileDescriptor(env, jfileDescriptor); 364 if (fd < 0) { 365 ALOGI("Invalid file descriptor"); 366 return NULL; 367 } 368 369 // Restore the file descriptor's offset on exiting this function. 370 AutoFDSeek autoRestore(fd); 371 372 int dupFd = dup(fd); 373 374 FILE* file = fdopen(dupFd, "r"); 375 if (file == NULL) { 376 ALOGI("Failed to open the file descriptor"); 377 return NULL; 378 } 379 380 if (fgets(jpegSignature.get(), kJpegSignatureSize, file) == NULL) { 381 ALOGI("Corrupted image."); 382 return NULL; 383 } 384 385 if (memcmp(jpegSignature.get(), kJpegSignatureChars, kJpegSignatureSize) == 0) { 386 ALOGI("Should be a JPEG stream."); 387 return NULL; 388 } 389 390 // Rewind the file descriptor. 391 fseek(file, 0L, SEEK_SET); 392 393 std::unique_ptr<SkFILEStream> fileStream(new SkFILEStream(file)); 394 return getRawAttributes(env, fileStream.get(), false); 395} 396 397static jobject ExifInterface_getRawAttributesFromInputStream( 398 JNIEnv* env, jclass /* clazz */, jobject jinputStream) { 399 jbyteArray byteArray = env->NewByteArray(8*1024); 400 ScopedLocalRef<jbyteArray> scoper(env, byteArray); 401 std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, jinputStream, scoper.get())); 402 return getRawAttributes(env, stream.get(), true); 403} 404 405} // extern "C" 406 407// ---------------------------------------------------------------------------- 408 409static JNINativeMethod gMethods[] = { 410 { "nativeInitRaw", "()V", (void *)ExifInterface_initRaw }, 411 { "nativeGetThumbnailFromAsset", "(JII)[B", (void *)ExifInterface_getThumbnailFromAsset }, 412 { "nativeGetRawAttributesFromAsset", "(J)Ljava/util/HashMap;", 413 (void*)ExifInterface_getRawAttributesFromAsset }, 414 { "nativeGetRawAttributesFromFileDescriptor", "(Ljava/io/FileDescriptor;)Ljava/util/HashMap;", 415 (void*)ExifInterface_getRawAttributesFromFileDescriptor }, 416 { "nativeGetRawAttributesFromInputStream", "(Ljava/io/InputStream;)Ljava/util/HashMap;", 417 (void*)ExifInterface_getRawAttributesFromInputStream }, 418}; 419 420int register_android_media_ExifInterface(JNIEnv *env) { 421 return AndroidRuntime::registerNativeMethods( 422 env, 423 "android/media/ExifInterface", 424 gMethods, 425 NELEM(gMethods)); 426} 427