1/*
2
3Copyright (c) 2008, The Android Open Source Project
4All rights reserved.
5
6Redistribution and use in source and binary forms, with or without
7modification, are permitted provided that the following conditions
8are met:
9 * Redistributions of source code must retain the above copyright
10   notice, this list of conditions and the following disclaimer.
11 * Redistributions in binary form must reproduce the above copyright
12   notice, this list of conditions and the following disclaimer in
13   the documentation and/or other materials provided with the
14   distribution.
15 * Neither the name of Google, Inc. nor the names of its contributors
16   may be used to endorse or promote products derived from this
17   software without specific prior written permission.
18
19THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
26OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
27AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30SUCH DAMAGE.
31
32*/
33
34#include <nativehelper/JNIHelp.h>
35#include <nativehelper/jni.h>
36
37#include <assert.h>
38#include <ctype.h>
39#include <dlfcn.h>
40#include <stdio.h>
41#include <string.h>
42#include <sys/stat.h>
43#include <utils/Log.h>
44
45#include "jhead.h"
46
47#ifndef NELEM
48#define NELEM(x) ((int)(sizeof(x) / sizeof((x)[0])))
49#endif
50
51// Define the line below to turn on poor man's debugging output
52#undef SUPERDEBUG
53
54// Various tests
55#undef REALLOCTEST
56#undef OUTOFMEMORYTEST1
57
58static void addExifAttibute(JNIEnv *env, jmethodID putMethod, jobject hashMap, char* key, char* value) {
59    jstring jkey = (*env)->NewStringUTF(env, key);
60    jstring jvalue = (*env)->NewStringUTF(env, value);
61
62    jobject jobject_of_entryset = (*env)->CallObjectMethod(env, hashMap, putMethod, jkey, jvalue);
63
64    (*env)->ReleaseStringUTFChars(env, jkey, key);
65    (*env)->ReleaseStringUTFChars(env, jvalue, value);
66}
67
68extern void ResetJpgfile();
69
70static int loadExifInfo(const char* FileName, int readJPG) {
71#ifdef SUPERDEBUG
72    LOGE("loadExifInfo");
73#endif
74    int Modified = FALSE;
75    ReadMode_t ReadMode = READ_METADATA;
76    if (readJPG) {
77        // Must add READ_IMAGE else we can't write the JPG back out.
78        ReadMode |= READ_IMAGE;
79    }
80
81#ifdef SUPERDEBUG
82    LOGE("ResetJpgfile");
83#endif
84    ResetJpgfile();
85
86    // Start with an empty image information structure.
87    memset(&ImageInfo, 0, sizeof(ImageInfo));
88    ImageInfo.FlashUsed = -1;
89    ImageInfo.MeteringMode = -1;
90    ImageInfo.Whitebalance = -1;
91
92    // Store file date/time.
93    {
94        struct stat st;
95        if (stat(FileName, &st) >= 0) {
96            ImageInfo.FileDateTime = st.st_mtime;
97            ImageInfo.FileSize = st.st_size;
98        }
99    }
100
101    strncpy(ImageInfo.FileName, FileName, PATH_MAX);
102#ifdef SUPERDEBUG
103    LOGE("ReadJpegFile");
104#endif
105    return ReadJpegFile(FileName, ReadMode);
106}
107
108static void saveJPGFile(const char* filename) {
109    char backupName[400];
110    struct stat buf;
111
112#ifdef SUPERDEBUG
113    LOGE("Modified: %s\n", filename);
114#endif
115
116    strncpy(backupName, filename, 395);
117    strcat(backupName, ".t");
118
119    // Remove any .old file name that may pre-exist
120#ifdef SUPERDEBUG
121    LOGE("removing backup %s", backupName);
122#endif
123    unlink(backupName);
124
125    // Rename the old file.
126#ifdef SUPERDEBUG
127    LOGE("rename %s to %s", filename, backupName);
128#endif
129    rename(filename, backupName);
130
131    // Write the new file.
132#ifdef SUPERDEBUG
133    LOGE("WriteJpegFile %s", filename);
134#endif
135    if (WriteJpegFile(filename)) {
136
137        // Copy the access rights from original file
138#ifdef SUPERDEBUG
139        LOGE("stating old file %s", backupName);
140#endif
141        if (stat(backupName, &buf) == 0){
142            // set Unix access rights and time to new file
143            struct utimbuf mtime;
144            chmod(filename, buf.st_mode);
145
146            mtime.actime = buf.st_mtime;
147            mtime.modtime = buf.st_mtime;
148
149            utime(filename, &mtime);
150        }
151
152        // Now that we are done, remove original file.
153#ifdef SUPERDEBUG
154        LOGE("unlinking old file %s", backupName);
155#endif
156        unlink(backupName);
157#ifdef SUPERDEBUG
158        LOGE("returning from saveJPGFile");
159#endif
160    } else {
161#ifdef SUPERDEBUG
162        LOGE("WriteJpegFile failed, restoring from backup file");
163#endif
164        // move back the backup file
165        rename(backupName, filename);
166    }
167}
168
169void copyThumbnailData(uchar* thumbnailData, int thumbnailLen) {
170#ifdef SUPERDEBUG
171    LOGE("******************************** copyThumbnailData\n");
172#endif
173    Section_t* ExifSection = FindSection(M_EXIF);
174    if (ExifSection == NULL) {
175        return;
176    }
177    int NewExifSize = ImageInfo.ThumbnailOffset+8+thumbnailLen;
178    ExifSection->Data = (uchar *)realloc(ExifSection->Data, NewExifSize);
179    if (ExifSection->Data == NULL) {
180        return;
181    }
182    uchar* ThumbnailPointer = ExifSection->Data+ImageInfo.ThumbnailOffset+8;
183
184    memcpy(ThumbnailPointer, thumbnailData, thumbnailLen);
185
186    ImageInfo.ThumbnailSize = thumbnailLen;
187
188    Put32u(ExifSection->Data+ImageInfo.ThumbnailSizeOffset+8, thumbnailLen);
189
190    ExifSection->Data[0] = (uchar)(NewExifSize >> 8);
191    ExifSection->Data[1] = (uchar)NewExifSize;
192    ExifSection->Size = NewExifSize;
193}
194
195static void saveAttributes(JNIEnv *env, jobject jobj, jstring jfilename, jstring jattributes)
196{
197#ifdef SUPERDEBUG
198    LOGE("******************************** saveAttributes\n");
199#endif
200    // format of attributes string passed from java:
201    // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
202    // example input: "4 ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
203    ExifElement_t* exifElementTable = NULL;
204    const char* filename = NULL;
205    uchar* thumbnailData = NULL;
206    int attrCnt = 0;
207    const char* attributes = (*env)->GetStringUTFChars(env, jattributes, NULL);
208    if (attributes == NULL) {
209        goto exit;
210    }
211#ifdef SUPERDEBUG
212    LOGE("attributes %s\n", attributes);
213#endif
214
215    // Get the number of attributes - it's the first number in the string.
216    attrCnt = atoi(attributes);
217    char* attrPtr = strchr(attributes, ' ') + 1;
218#ifdef SUPERDEBUG
219    LOGE("attribute count %d attrPtr %s\n", attrCnt, attrPtr);
220#endif
221
222    // Load all the hash exif elements into a more c-like structure
223    exifElementTable = malloc(sizeof(ExifElement_t) * attrCnt);
224    if (exifElementTable == NULL) {
225        goto exit;
226    }
227#ifdef OUTOFMEMORYTEST1
228    goto exit;
229#endif
230
231    int i;
232    char tag[100];
233    int gpsTagCount = 0;
234    int exifTagCount = 0;
235
236    for (i = 0; i < attrCnt; i++) {
237        // get an element from the attribute string and add it to the c structure
238        // first, extract the attribute name
239        char* tagEnd = strchr(attrPtr, '=');
240        if (tagEnd == 0) {
241#ifdef SUPERDEBUG
242            LOGE("saveAttributes: couldn't find end of tag");
243#endif
244            goto exit;
245        }
246        if (tagEnd - attrPtr > 99) {
247#ifdef SUPERDEBUG
248            LOGE("saveAttributes: attribute tag way too long");
249#endif
250            goto exit;
251        }
252        memcpy(tag, attrPtr, tagEnd - attrPtr);
253        tag[tagEnd - attrPtr] = 0;
254
255        if (IsGpsTag(tag)) {
256            exifElementTable[i].GpsTag = TRUE;
257            exifElementTable[i].Tag = GpsTagNameToValue(tag);
258            ++gpsTagCount;
259        } else {
260            exifElementTable[i].GpsTag = FALSE;
261            exifElementTable[i].Tag = TagNameToValue(tag);
262            ++exifTagCount;
263        }
264        attrPtr = tagEnd + 1;
265
266        // next get the length of the attribute value
267        int valueLen = atoi(attrPtr);
268        attrPtr = strchr(attrPtr, ' ') + 1;
269        if (attrPtr == 0) {
270#ifdef SUPERDEBUG
271            LOGE("saveAttributes: couldn't find end of value len");
272#endif
273            goto exit;
274        }
275        exifElementTable[i].Value = malloc(valueLen + 1);
276        if (exifElementTable[i].Value == NULL) {
277            goto exit;
278        }
279        memcpy(exifElementTable[i].Value, attrPtr, valueLen);
280        exifElementTable[i].Value[valueLen] = 0;
281        exifElementTable[i].DataLength = valueLen;
282
283        attrPtr += valueLen;
284
285#ifdef SUPERDEBUG
286        LOGE("tag %s id %d value %s data length=%d isGps=%d", tag, exifElementTable[i].Tag,
287            exifElementTable[i].Value, exifElementTable[i].DataLength, exifElementTable[i].GpsTag);
288#endif
289    }
290
291    filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
292#ifdef SUPERDEBUG
293    LOGE("Call loadAttributes() with filename is %s. Loading exif info\n", filename);
294#endif
295    loadExifInfo(filename, TRUE);
296
297#ifdef SUPERDEBUG
298//    DumpExifMap = TRUE;
299    ShowTags = TRUE;
300    ShowImageInfo(TRUE);
301    LOGE("create exif 2");
302#endif
303
304    // If the jpg file has a thumbnail, preserve it.
305    int thumbnailLength = ImageInfo.ThumbnailSize;
306    if (ImageInfo.ThumbnailOffset) {
307        Section_t* ExifSection = FindSection(M_EXIF);
308        if (ExifSection) {
309            uchar* thumbnailPointer = ExifSection->Data + ImageInfo.ThumbnailOffset + 8;
310            thumbnailData = (uchar*)malloc(ImageInfo.ThumbnailSize);
311            // if the malloc fails, we just won't copy the thumbnail
312            if (thumbnailData) {
313                memcpy(thumbnailData, thumbnailPointer, thumbnailLength);
314            }
315        }
316    }
317
318    create_EXIF(exifElementTable, exifTagCount, gpsTagCount);
319
320    if (thumbnailData) {
321        copyThumbnailData(thumbnailData, thumbnailLength);
322    }
323
324exit:
325#ifdef SUPERDEBUG
326    LOGE("cleaning up now in saveAttributes");
327#endif
328    // try to clean up resources
329    if (attributes) {
330        (*env)->ReleaseStringUTFChars(env, jattributes, attributes);
331    }
332    if (filename) {
333        (*env)->ReleaseStringUTFChars(env, jfilename, filename);
334    }
335    if (exifElementTable) {
336        // free the table
337        for (i = 0; i < attrCnt; i++) {
338            free(exifElementTable[i].Value);
339        }
340        free(exifElementTable);
341    }
342    if (thumbnailData) {
343        free(thumbnailData);
344    }
345#ifdef SUPERDEBUG
346    LOGE("returning from saveAttributes");
347#endif
348
349// Temporarily saving these commented out lines because they represent a lot of figuring out
350// patterns for JNI.
351//    // Get link to Method "entrySet"
352//    jmethodID entrySetMethod = (*env)->GetMethodID(env, jclass_of_hashmap, "entrySet", "()Ljava/util/Set;");
353//
354//    // Invoke the "entrySet" method on the HashMap object
355//    jobject jobject_of_entryset = (*env)->CallObjectMethod(env, hashMap, entrySetMethod);
356//
357//    // Get the Set Class
358//    jclass jclass_of_set = (*env)->FindClass(env, "java/util/Set");
359//
360//    if (jclass_of_set == 0) {
361//        printf("java/util/Set lookup failed\n");
362//        return;
363//    }
364//
365//    // Get link to Method "iterator"
366//    jmethodID iteratorMethod = (*env)->GetMethodID(env, jclass_of_set, "iterator", "()Ljava/util/Iterator;");
367//
368//    // Invoke the "iterator" method on the jobject_of_entryset variable of type Set
369//    jobject jobject_of_iterator = (*env)->CallObjectMethod(env, jobject_of_entryset, iteratorMethod);
370//
371//    // Get the "Iterator" class
372//    jclass jclass_of_iterator = (*env)->FindClass(env, "java/util/Iterator");
373//
374//    // Get link to Method "hasNext"
375//    jmethodID hasNextMethod = (*env)->GetMethodID(env, jclass_of_iterator, "hasNext", "()Z");
376//
377//    // Invoke - Get the value hasNextMethod
378//    jboolean bHasNext = (*env)->CallBooleanMethod(env, jobject_of_iterator, hasNextMethod);
379
380//    // Get link to Method "hasNext"
381//    jmethodID nextMethod = (*env)->GetMethodID(env, jclass_of_iterator, "next", "()Ljava/util/Map/Entry;");
382//
383//    jclass jclass_of_mapentry = (*env)->FindClass(env, "java/util/Map/Entry");
384//
385//    jmethodID getKeyMethod = (*env)->GetMethodID(env, jclass_of_mapentry, "getKey", "()Ljava/lang/Object");
386//
387//    jmethodID getValueMethod = (*env)->GetMethodID(env, jclass_of_mapentry, "getValue", "()Ljava/lang/Object");
388}
389
390static jboolean appendThumbnail(JNIEnv *env, jobject jobj, jstring jfilename, jstring jthumbnailfilename)
391{
392#ifdef SUPERDEBUG
393    LOGE("******************************** appendThumbnail\n");
394#endif
395
396    const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
397    if (filename == NULL) {
398        return JNI_FALSE;
399    }
400    const char* thumbnailfilename = (*env)->GetStringUTFChars(env, jthumbnailfilename, NULL);
401    if (thumbnailfilename == NULL) {
402        return JNI_FALSE;
403    }
404 #ifdef SUPERDEBUG
405     LOGE("*******before actual call to ReplaceThumbnail\n");
406     ShowImageInfo(TRUE);
407 #endif
408    ReplaceThumbnail(thumbnailfilename);
409 #ifdef SUPERDEBUG
410     ShowImageInfo(TRUE);
411 #endif
412    (*env)->ReleaseStringUTFChars(env, jfilename, filename);
413    (*env)->ReleaseStringUTFChars(env, jthumbnailfilename, thumbnailfilename);
414
415    DiscardData();
416    return JNI_TRUE;
417}
418
419static void commitChanges(JNIEnv *env, jobject jobj, jstring jfilename)
420{
421#ifdef SUPERDEBUG
422    LOGE("******************************** commitChanges\n");
423#endif
424    const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
425    if (filename) {
426        saveJPGFile(filename);
427        DiscardData();
428        (*env)->ReleaseStringUTFChars(env, jfilename, filename);
429    }
430}
431
432static jbyteArray getThumbnail(JNIEnv *env, jobject jobj, jstring jfilename)
433{
434#ifdef SUPERDEBUG
435    LOGE("******************************** getThumbnail\n");
436#endif
437
438    const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
439    if (filename) {
440        loadExifInfo(filename, FALSE);
441        Section_t* ExifSection = FindSection(M_EXIF);
442        if (ExifSection == NULL ||  ImageInfo.ThumbnailSize == 0) {
443#ifdef SUPERDEBUG
444    LOGE("no exif section or size == 0, so no thumbnail\n");
445#endif
446            goto noThumbnail;
447        }
448        uchar* thumbnailPointer = ExifSection->Data + ImageInfo.ThumbnailOffset + 8;
449
450        jbyteArray byteArray = (*env)->NewByteArray(env, ImageInfo.ThumbnailSize);
451        if (byteArray == NULL) {
452#ifdef SUPERDEBUG
453    LOGE("couldn't allocate thumbnail memory, so no thumbnail\n");
454#endif
455            goto noThumbnail;
456        }
457        (*env)->SetByteArrayRegion(env, byteArray, 0, ImageInfo.ThumbnailSize, thumbnailPointer);
458#ifdef SUPERDEBUG
459    LOGE("thumbnail size %d\n", ImageInfo.ThumbnailSize);
460#endif
461        (*env)->ReleaseStringUTFChars(env, jfilename, filename);
462        DiscardData();
463        return byteArray;
464    }
465noThumbnail:
466    if (filename) {
467        (*env)->ReleaseStringUTFChars(env, jfilename, filename);
468    }
469    DiscardData();
470    return NULL;
471}
472
473static int attributeCount;      // keep track of how many attributes we've added
474
475// returns new buffer length
476static int addKeyValueString(char** buf, int bufLen, const char* key, const char* value) {
477    // Appends to buf like this: "ImageLength=4 1024"
478
479    char valueLen[15];
480    snprintf(valueLen, 15, "=%d ", (int)strlen(value));
481
482    // check to see if buf has enough room to append
483    int len = strlen(key) + strlen(valueLen) + strlen(value);
484    int newLen = strlen(*buf) + len;
485    if (newLen >= bufLen) {
486#ifdef REALLOCTEST
487        bufLen = newLen + 5;
488        LOGE("reallocing to %d", bufLen);
489#else
490        bufLen = newLen + 500;
491#endif
492        *buf = realloc(*buf, bufLen);
493        if (*buf == NULL) {
494            return 0;
495        }
496    }
497    // append the new attribute and value
498    snprintf(*buf + strlen(*buf), bufLen, "%s%s%s", key, valueLen, value);
499#ifdef SUPERDEBUG
500    LOGE("buf %s", *buf);
501#endif
502    ++attributeCount;
503    return bufLen;
504}
505
506// returns new buffer length
507static int addKeyValueInt(char** buf, int bufLen, const char* key, int value) {
508    char valueStr[20];
509    snprintf(valueStr, 20, "%d", value);
510
511    return addKeyValueString(buf, bufLen, key, valueStr);
512}
513
514// returns new buffer length
515static int addKeyValueDouble(char** buf, int bufLen, const char* key, double value, const char* format) {
516    char valueStr[30];
517    snprintf(valueStr, 30, format, value);
518
519    return addKeyValueString(buf, bufLen, key, valueStr);
520}
521
522// Returns new buffer length. Rational value will be appended as "numerator/denominator".
523static int addKeyValueRational(char** buf, int bufLen, const char* key, rat_t value) {
524    char valueStr[25];
525    snprintf(valueStr, sizeof(valueStr), "%u/%u", value.num, value.denom);
526    return addKeyValueString(buf, bufLen, key, valueStr);
527}
528
529static jstring getAttributes(JNIEnv *env, jobject jobj, jstring jfilename)
530{
531#ifdef SUPERDEBUG
532    LOGE("******************************** getAttributes\n");
533#endif
534    const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
535    loadExifInfo(filename, FALSE);
536#ifdef SUPERDEBUG
537    ShowImageInfo(TRUE);
538#endif
539    (*env)->ReleaseStringUTFChars(env, jfilename, filename);
540
541    attributeCount = 0;
542#ifdef REALLOCTEST
543    int bufLen = 5;
544#else
545    int bufLen = 1000;
546#endif
547    char* buf = malloc(bufLen);
548    if (buf == NULL) {
549        return NULL;
550    }
551    *buf = 0;   // start the string out at zero length
552
553    // save a fake "hasThumbnail" tag to pass to the java ExifInterface
554    bufLen = addKeyValueString(&buf, bufLen, "hasThumbnail",
555        ImageInfo.ThumbnailOffset == 0 || ImageInfo.ThumbnailAtEnd == FALSE || ImageInfo.ThumbnailSize == 0 ?
556            "false" : "true");
557    if (bufLen == 0) return NULL;
558
559    if (ImageInfo.CameraMake[0]) {
560        bufLen = addKeyValueString(&buf, bufLen, "Make", ImageInfo.CameraMake);
561        if (bufLen == 0) return NULL;
562    }
563    if (ImageInfo.CameraModel[0]) {
564        bufLen = addKeyValueString(&buf, bufLen, "Model", ImageInfo.CameraModel);
565        if (bufLen == 0) return NULL;
566    }
567    if (ImageInfo.DateTime[0]) {
568        bufLen = addKeyValueString(&buf, bufLen, "DateTime", ImageInfo.DateTime);
569        if (bufLen == 0) return NULL;
570    }
571    bufLen = addKeyValueInt(&buf, bufLen, "ImageWidth", ImageInfo.Width);
572    if (bufLen == 0) return NULL;
573
574    bufLen = addKeyValueInt(&buf, bufLen, "ImageLength", ImageInfo.Height);
575    if (bufLen == 0) return NULL;
576
577    bufLen = addKeyValueInt(&buf, bufLen, "Orientation", ImageInfo.Orientation);
578    if (bufLen == 0) return NULL;
579
580    if (ImageInfo.FlashUsed >= 0) {
581        bufLen = addKeyValueInt(&buf, bufLen, "Flash", ImageInfo.FlashUsed);
582        if (bufLen == 0) return NULL;
583    }
584
585    if (ImageInfo.FocalLength.num != 0 && ImageInfo.FocalLength.denom != 0) {
586        bufLen = addKeyValueRational(&buf, bufLen, "FocalLength", ImageInfo.FocalLength);
587        if (bufLen == 0) return NULL;
588    }
589
590    if (ImageInfo.DigitalZoomRatio > 1.0){
591        // Digital zoom used.  Shame on you!
592        bufLen = addKeyValueDouble(&buf, bufLen, "DigitalZoomRatio", ImageInfo.DigitalZoomRatio, "%1.3f");
593        if (bufLen == 0) return NULL;
594    }
595
596    if (ImageInfo.ExposureTime){
597        const char* format;
598        if (ImageInfo.ExposureTime < 0.010){
599            format = "%6.4f";
600        } else {
601            format = "%5.3f";
602        }
603
604        bufLen = addKeyValueDouble(&buf, bufLen, "ExposureTime", (double)ImageInfo.ExposureTime, format);
605        if (bufLen == 0) return NULL;
606    }
607
608    if (ImageInfo.ApertureFNumber){
609        bufLen = addKeyValueDouble(&buf, bufLen, "FNumber", (double)ImageInfo.ApertureFNumber, "%3.1f");
610        if (bufLen == 0) return NULL;
611    }
612
613    if (ImageInfo.Distance){
614        bufLen = addKeyValueDouble(&buf, bufLen, "SubjectDistance", (double)ImageInfo.Distance, "%4.2f");
615        if (bufLen == 0) return NULL;
616    }
617
618    if (ImageInfo.ISOequivalent){
619        bufLen = addKeyValueInt(&buf, bufLen, "ISOSpeedRatings", ImageInfo.ISOequivalent);
620        if (bufLen == 0) return NULL;
621    }
622
623    if (ImageInfo.ExposureBias){
624        // If exposure bias was specified, but set to zero, presumably its no bias at all,
625        // so only show it if its nonzero.
626        bufLen = addKeyValueDouble(&buf, bufLen, "ExposureBiasValue", (double)ImageInfo.ExposureBias, "%4.2f");
627        if (bufLen == 0) return NULL;
628    }
629
630    if (ImageInfo.Whitebalance >= 0) {
631        bufLen = addKeyValueInt(&buf, bufLen, "WhiteBalance", ImageInfo.Whitebalance);
632        if (bufLen == 0) return NULL;
633    }
634
635    bufLen = addKeyValueInt(&buf, bufLen, "LightSource", ImageInfo.LightSource);
636    if (bufLen == 0) return NULL;
637
638
639    if (ImageInfo.MeteringMode) {
640        bufLen = addKeyValueInt(&buf, bufLen, "MeteringMode", ImageInfo.MeteringMode);
641        if (bufLen == 0) return NULL;
642    }
643
644    if (ImageInfo.ExposureProgram) {
645        bufLen = addKeyValueInt(&buf, bufLen, "ExposureProgram", ImageInfo.ExposureProgram);
646        if (bufLen == 0) return NULL;
647    }
648
649    if (ImageInfo.ExposureMode) {
650        bufLen = addKeyValueInt(&buf, bufLen, "ExposureMode", ImageInfo.ExposureMode);
651        if (bufLen == 0) return NULL;
652    }
653
654    if (ImageInfo.GpsInfoPresent) {
655        if (ImageInfo.GpsLatRaw[0]) {
656            bufLen = addKeyValueString(&buf, bufLen, "GPSLatitude", ImageInfo.GpsLatRaw);
657            if (bufLen == 0) return NULL;
658        }
659        if (ImageInfo.GpsLatRef[0]) {
660            bufLen = addKeyValueString(&buf, bufLen, "GPSLatitudeRef", ImageInfo.GpsLatRef);
661            if (bufLen == 0) return NULL;
662        }
663        if (ImageInfo.GpsLongRaw[0]) {
664            bufLen = addKeyValueString(&buf, bufLen, "GPSLongitude", ImageInfo.GpsLongRaw);
665            if (bufLen == 0) return NULL;
666        }
667        if (ImageInfo.GpsLongRef[0]) {
668            bufLen = addKeyValueString(&buf, bufLen, "GPSLongitudeRef", ImageInfo.GpsLongRef);
669            if (bufLen == 0) return NULL;
670        }
671        if (ImageInfo.GpsAlt[0]) {
672            bufLen = addKeyValueRational(&buf, bufLen, "GPSAltitude", ImageInfo.GpsAltRaw);
673            bufLen = addKeyValueInt(&buf, bufLen, "GPSAltitudeRef", ImageInfo.GpsAltRef);
674            if (bufLen == 0) return NULL;
675        }
676        if (ImageInfo.GpsDateStamp[0]) {
677            bufLen = addKeyValueString(&buf, bufLen, "GPSDateStamp", ImageInfo.GpsDateStamp);
678            if (bufLen == 0) return NULL;
679        }
680        if (ImageInfo.GpsTimeStamp[0]) {
681            bufLen = addKeyValueString(&buf, bufLen, "GPSTimeStamp", ImageInfo.GpsTimeStamp);
682            if (bufLen == 0) return NULL;
683        }
684        if (ImageInfo.GpsProcessingMethod[0]) {
685            bufLen = addKeyValueString(&buf, bufLen, "GPSProcessingMethod", ImageInfo.GpsProcessingMethod);
686            if (bufLen == 0) return NULL;
687        }
688    }
689
690    if (ImageInfo.Comments[0]) {
691        bufLen = addKeyValueString(&buf, bufLen, "UserComment", ImageInfo.Comments);
692        if (bufLen == 0) return NULL;
693    }
694
695    // put the attribute count at the beginnnig of the string
696    int finalBufLen = strlen(buf) + 20;
697    char* finalResult = malloc(finalBufLen);
698    if (finalResult == NULL) {
699        free(buf);
700        return NULL;
701    }
702    snprintf(finalResult, finalBufLen, "%d %s", attributeCount, buf);
703    int k;
704    for (k = 0; k < finalBufLen; k++)
705        if (!isascii(finalResult[k]))
706            finalResult[k] = '?';
707    free(buf);
708
709#ifdef SUPERDEBUG
710    LOGE("*********Returning result \"%s\"", finalResult);
711#endif
712    jstring result = ((*env)->NewStringUTF(env, finalResult));
713    free(finalResult);
714    DiscardData();
715    return result;
716}
717
718static const char *classPathName = "android/media/ExifInterface";
719
720static JNINativeMethod methods[] = {
721  {"saveAttributesNative", "(Ljava/lang/String;Ljava/lang/String;)V", (void*)saveAttributes },
722  {"getAttributesNative", "(Ljava/lang/String;)Ljava/lang/String;", (void*)getAttributes },
723  {"appendThumbnailNative", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)appendThumbnail },
724  {"commitChangesNative", "(Ljava/lang/String;)V", (void*)commitChanges },
725  {"getThumbnailNative", "(Ljava/lang/String;)[B", (void*)getThumbnail },
726};
727
728/*
729 * Register several native methods for one class.
730 */
731static int registerNativeMethods(JNIEnv* env, const char* className,
732    JNINativeMethod* gMethods, int numMethods)
733{
734    jclass clazz;
735
736    clazz = (*env)->FindClass(env, className);
737    if (clazz == NULL) {
738        fprintf(stderr,
739            "Native registration unable to find class '%s'\n", className);
740        return JNI_FALSE;
741    }
742    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
743        fprintf(stderr, "RegisterNatives failed for '%s'\n", className);
744        return JNI_FALSE;
745    }
746
747    return JNI_TRUE;
748}
749
750/*
751 * Register native methods for all classes we know about.
752 */
753static int registerNatives(JNIEnv* env)
754{
755    return jniRegisterNativeMethods(env, classPathName,
756                                    methods, NELEM(methods));
757}
758
759/*
760 * Set some test stuff up.
761 *
762 * Returns the JNI version on success, -1 on failure.
763 */
764__attribute__ ((visibility("default"))) jint JNI_OnLoad(JavaVM* vm, void* reserved)
765{
766    JNIEnv* env = NULL;
767    jint result = -1;
768
769    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
770        fprintf(stderr, "ERROR: GetEnv failed\n");
771        goto bail;
772    }
773    assert(env != NULL);
774
775    printf("In mgmain JNI_OnLoad\n");
776
777    if (registerNatives(env) < 0) {
778        fprintf(stderr, "ERROR: Exif native registration failed\n");
779        goto bail;
780    }
781
782    /* success -- return valid version number */
783    result = JNI_VERSION_1_4;
784
785bail:
786    return result;
787}
788