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