main.c revision 482486a3d21c4d4c933f3081ff14122292314bc7
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    ALOGE("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    ALOGE("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    ALOGE("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    ALOGE("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    ALOGE("removing backup %s", backupName);
122#endif
123    unlink(backupName);
124
125    // Rename the old file.
126#ifdef SUPERDEBUG
127    ALOGE("rename %s to %s", filename, backupName);
128#endif
129    rename(filename, backupName);
130
131    // Write the new file.
132#ifdef SUPERDEBUG
133    ALOGE("WriteJpegFile %s", filename);
134#endif
135    if (WriteJpegFile(filename)) {
136
137        // Copy the access rights from original file
138#ifdef SUPERDEBUG
139        ALOGE("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        ALOGE("unlinking old file %s", backupName);
155#endif
156        unlink(backupName);
157#ifdef SUPERDEBUG
158        ALOGE("returning from saveJPGFile");
159#endif
160    } else {
161#ifdef SUPERDEBUG
162        ALOGE("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    ALOGE("******************************** 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    ALOGE("******************************** 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    ALOGE("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    ALOGE("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 hasDateTimeTag = FALSE;
234    int gpsTagCount = 0;
235    int exifTagCount = 0;
236
237    for (i = 0; i < attrCnt; i++) {
238        // get an element from the attribute string and add it to the c structure
239        // first, extract the attribute name
240        char* tagEnd = strchr(attrPtr, '=');
241        if (tagEnd == 0) {
242#ifdef SUPERDEBUG
243            ALOGE("saveAttributes: couldn't find end of tag");
244#endif
245            goto exit;
246        }
247        if (tagEnd - attrPtr > 99) {
248#ifdef SUPERDEBUG
249            ALOGE("saveAttributes: attribute tag way too long");
250#endif
251            goto exit;
252        }
253        memcpy(tag, attrPtr, tagEnd - attrPtr);
254        tag[tagEnd - attrPtr] = 0;
255
256        if (IsGpsTag(tag)) {
257            exifElementTable[i].GpsTag = TRUE;
258            exifElementTable[i].Tag = GpsTagNameToValue(tag);
259            ++gpsTagCount;
260        } else {
261            exifElementTable[i].GpsTag = FALSE;
262            exifElementTable[i].Tag = TagNameToValue(tag);
263            ++exifTagCount;
264        }
265        attrPtr = tagEnd + 1;
266
267        if (IsDateTimeTag(exifElementTable[i].Tag)) {
268            hasDateTimeTag = TRUE;
269        }
270
271        // next get the length of the attribute value
272        int valueLen = atoi(attrPtr);
273        attrPtr = strchr(attrPtr, ' ') + 1;
274        if (attrPtr == 0) {
275#ifdef SUPERDEBUG
276            ALOGE("saveAttributes: couldn't find end of value len");
277#endif
278            goto exit;
279        }
280        exifElementTable[i].Value = malloc(valueLen + 1);
281        if (exifElementTable[i].Value == NULL) {
282            goto exit;
283        }
284        memcpy(exifElementTable[i].Value, attrPtr, valueLen);
285        exifElementTable[i].Value[valueLen] = 0;
286        exifElementTable[i].DataLength = valueLen;
287
288        attrPtr += valueLen;
289
290#ifdef SUPERDEBUG
291        ALOGE("tag %s id %d value %s data length=%d isGps=%d", tag, exifElementTable[i].Tag,
292            exifElementTable[i].Value, exifElementTable[i].DataLength, exifElementTable[i].GpsTag);
293#endif
294    }
295
296    filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
297#ifdef SUPERDEBUG
298    ALOGE("Call loadAttributes() with filename is %s. Loading exif info\n", filename);
299#endif
300    loadExifInfo(filename, TRUE);
301
302#ifdef SUPERDEBUG
303//    DumpExifMap = TRUE;
304    ShowTags = TRUE;
305    ShowImageInfo(TRUE);
306    ALOGE("create exif 2");
307#endif
308
309    // If the jpg file has a thumbnail, preserve it.
310    int thumbnailLength = ImageInfo.ThumbnailSize;
311    if (ImageInfo.ThumbnailOffset) {
312        Section_t* ExifSection = FindSection(M_EXIF);
313        if (ExifSection) {
314            uchar* thumbnailPointer = ExifSection->Data + ImageInfo.ThumbnailOffset + 8;
315            thumbnailData = (uchar*)malloc(ImageInfo.ThumbnailSize);
316            // if the malloc fails, we just won't copy the thumbnail
317            if (thumbnailData) {
318                memcpy(thumbnailData, thumbnailPointer, thumbnailLength);
319            }
320        }
321    }
322
323    create_EXIF(exifElementTable, exifTagCount, gpsTagCount, hasDateTimeTag);
324
325    if (thumbnailData) {
326        copyThumbnailData(thumbnailData, thumbnailLength);
327    }
328
329exit:
330#ifdef SUPERDEBUG
331    ALOGE("cleaning up now in saveAttributes");
332#endif
333    // try to clean up resources
334    if (attributes) {
335        (*env)->ReleaseStringUTFChars(env, jattributes, attributes);
336    }
337    if (filename) {
338        (*env)->ReleaseStringUTFChars(env, jfilename, filename);
339    }
340    if (exifElementTable) {
341        // free the table
342        for (i = 0; i < attrCnt; i++) {
343            free(exifElementTable[i].Value);
344        }
345        free(exifElementTable);
346    }
347    if (thumbnailData) {
348        free(thumbnailData);
349    }
350#ifdef SUPERDEBUG
351    ALOGE("returning from saveAttributes");
352#endif
353
354// Temporarily saving these commented out lines because they represent a lot of figuring out
355// patterns for JNI.
356//    // Get link to Method "entrySet"
357//    jmethodID entrySetMethod = (*env)->GetMethodID(env, jclass_of_hashmap, "entrySet", "()Ljava/util/Set;");
358//
359//    // Invoke the "entrySet" method on the HashMap object
360//    jobject jobject_of_entryset = (*env)->CallObjectMethod(env, hashMap, entrySetMethod);
361//
362//    // Get the Set Class
363//    jclass jclass_of_set = (*env)->FindClass(env, "java/util/Set");
364//
365//    if (jclass_of_set == 0) {
366//        printf("java/util/Set lookup failed\n");
367//        return;
368//    }
369//
370//    // Get link to Method "iterator"
371//    jmethodID iteratorMethod = (*env)->GetMethodID(env, jclass_of_set, "iterator", "()Ljava/util/Iterator;");
372//
373//    // Invoke the "iterator" method on the jobject_of_entryset variable of type Set
374//    jobject jobject_of_iterator = (*env)->CallObjectMethod(env, jobject_of_entryset, iteratorMethod);
375//
376//    // Get the "Iterator" class
377//    jclass jclass_of_iterator = (*env)->FindClass(env, "java/util/Iterator");
378//
379//    // Get link to Method "hasNext"
380//    jmethodID hasNextMethod = (*env)->GetMethodID(env, jclass_of_iterator, "hasNext", "()Z");
381//
382//    // Invoke - Get the value hasNextMethod
383//    jboolean bHasNext = (*env)->CallBooleanMethod(env, jobject_of_iterator, hasNextMethod);
384
385//    // Get link to Method "hasNext"
386//    jmethodID nextMethod = (*env)->GetMethodID(env, jclass_of_iterator, "next", "()Ljava/util/Map/Entry;");
387//
388//    jclass jclass_of_mapentry = (*env)->FindClass(env, "java/util/Map/Entry");
389//
390//    jmethodID getKeyMethod = (*env)->GetMethodID(env, jclass_of_mapentry, "getKey", "()Ljava/lang/Object");
391//
392//    jmethodID getValueMethod = (*env)->GetMethodID(env, jclass_of_mapentry, "getValue", "()Ljava/lang/Object");
393}
394
395static jboolean appendThumbnail(JNIEnv *env, jobject jobj, jstring jfilename, jstring jthumbnailfilename)
396{
397#ifdef SUPERDEBUG
398    ALOGE("******************************** appendThumbnail\n");
399#endif
400
401    const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
402    if (filename == NULL) {
403        return JNI_FALSE;
404    }
405    const char* thumbnailfilename = (*env)->GetStringUTFChars(env, jthumbnailfilename, NULL);
406    if (thumbnailfilename == NULL) {
407        return JNI_FALSE;
408    }
409 #ifdef SUPERDEBUG
410     ALOGE("*******before actual call to ReplaceThumbnail\n");
411     ShowImageInfo(TRUE);
412 #endif
413    ReplaceThumbnail(thumbnailfilename);
414 #ifdef SUPERDEBUG
415     ShowImageInfo(TRUE);
416 #endif
417    (*env)->ReleaseStringUTFChars(env, jfilename, filename);
418    (*env)->ReleaseStringUTFChars(env, jthumbnailfilename, thumbnailfilename);
419
420    DiscardData();
421    return JNI_TRUE;
422}
423
424static void commitChanges(JNIEnv *env, jobject jobj, jstring jfilename)
425{
426#ifdef SUPERDEBUG
427    ALOGE("******************************** commitChanges\n");
428#endif
429    const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
430    if (filename) {
431        saveJPGFile(filename);
432        DiscardData();
433        (*env)->ReleaseStringUTFChars(env, jfilename, filename);
434    }
435}
436
437static jbyteArray getThumbnail(JNIEnv *env, jobject jobj, jstring jfilename)
438{
439#ifdef SUPERDEBUG
440    ALOGE("******************************** getThumbnail\n");
441#endif
442
443    const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
444    if (filename) {
445        loadExifInfo(filename, FALSE);
446        Section_t* ExifSection = FindSection(M_EXIF);
447        if (ExifSection == NULL ||  ImageInfo.ThumbnailSize == 0) {
448#ifdef SUPERDEBUG
449    ALOGE("no exif section or size == 0, so no thumbnail\n");
450#endif
451            goto noThumbnail;
452        }
453        uchar* thumbnailPointer = ExifSection->Data + ImageInfo.ThumbnailOffset + 8;
454
455        jbyteArray byteArray = (*env)->NewByteArray(env, ImageInfo.ThumbnailSize);
456        if (byteArray == NULL) {
457#ifdef SUPERDEBUG
458    ALOGE("couldn't allocate thumbnail memory, so no thumbnail\n");
459#endif
460            goto noThumbnail;
461        }
462        (*env)->SetByteArrayRegion(env, byteArray, 0, ImageInfo.ThumbnailSize, thumbnailPointer);
463#ifdef SUPERDEBUG
464    ALOGE("thumbnail size %d\n", ImageInfo.ThumbnailSize);
465#endif
466        (*env)->ReleaseStringUTFChars(env, jfilename, filename);
467        DiscardData();
468        return byteArray;
469    }
470noThumbnail:
471    if (filename) {
472        (*env)->ReleaseStringUTFChars(env, jfilename, filename);
473    }
474    DiscardData();
475    return NULL;
476}
477
478static int attributeCount;      // keep track of how many attributes we've added
479
480// returns new buffer length
481static int addKeyValueString(char** buf, int bufLen, const char* key, const char* value) {
482    // Appends to buf like this: "ImageLength=4 1024"
483
484    char valueLen[15];
485    snprintf(valueLen, 15, "=%d ", (int)strlen(value));
486
487    // check to see if buf has enough room to append
488    int len = strlen(key) + strlen(valueLen) + strlen(value);
489    int newLen = strlen(*buf) + len;
490    if (newLen >= bufLen) {
491#ifdef REALLOCTEST
492        bufLen = newLen + 5;
493        ALOGE("reallocing to %d", bufLen);
494#else
495        bufLen = newLen + 500;
496#endif
497        *buf = realloc(*buf, bufLen);
498        if (*buf == NULL) {
499            return 0;
500        }
501    }
502    // append the new attribute and value
503    snprintf(*buf + strlen(*buf), bufLen, "%s%s%s", key, valueLen, value);
504#ifdef SUPERDEBUG
505    ALOGE("buf %s", *buf);
506#endif
507    ++attributeCount;
508    return bufLen;
509}
510
511// returns new buffer length
512static int addKeyValueInt(char** buf, int bufLen, const char* key, int value) {
513    char valueStr[20];
514    snprintf(valueStr, 20, "%d", value);
515
516    return addKeyValueString(buf, bufLen, key, valueStr);
517}
518
519// returns new buffer length
520static int addKeyValueDouble(char** buf, int bufLen, const char* key, double value, const char* format) {
521    char valueStr[30];
522    snprintf(valueStr, 30, format, value);
523
524    return addKeyValueString(buf, bufLen, key, valueStr);
525}
526
527// Returns new buffer length. Rational value will be appended as "numerator/denominator".
528static int addKeyValueRational(char** buf, int bufLen, const char* key, rat_t value) {
529    char valueStr[25];
530    snprintf(valueStr, sizeof(valueStr), "%u/%u", value.num, value.denom);
531    return addKeyValueString(buf, bufLen, key, valueStr);
532}
533
534static jstring getAttributes(JNIEnv *env, jobject jobj, jstring jfilename)
535{
536#ifdef SUPERDEBUG
537    ALOGE("******************************** getAttributes\n");
538#endif
539    const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
540    loadExifInfo(filename, FALSE);
541#ifdef SUPERDEBUG
542    ShowImageInfo(TRUE);
543#endif
544    (*env)->ReleaseStringUTFChars(env, jfilename, filename);
545
546    attributeCount = 0;
547#ifdef REALLOCTEST
548    int bufLen = 5;
549#else
550    int bufLen = 1000;
551#endif
552    char* buf = malloc(bufLen);
553    if (buf == NULL) {
554        return NULL;
555    }
556    *buf = 0;   // start the string out at zero length
557
558    // save a fake "hasThumbnail" tag to pass to the java ExifInterface
559    bufLen = addKeyValueString(&buf, bufLen, "hasThumbnail",
560        ImageInfo.ThumbnailOffset == 0 || ImageInfo.ThumbnailAtEnd == FALSE || ImageInfo.ThumbnailSize == 0 ?
561            "false" : "true");
562    if (bufLen == 0) return NULL;
563
564    if (ImageInfo.CameraMake[0]) {
565        bufLen = addKeyValueString(&buf, bufLen, "Make", ImageInfo.CameraMake);
566        if (bufLen == 0) return NULL;
567    }
568    if (ImageInfo.CameraModel[0]) {
569        bufLen = addKeyValueString(&buf, bufLen, "Model", ImageInfo.CameraModel);
570        if (bufLen == 0) return NULL;
571    }
572    if (ImageInfo.DateTime[0]) {
573        bufLen = addKeyValueString(&buf, bufLen, "DateTime", ImageInfo.DateTime);
574        if (bufLen == 0) return NULL;
575    }
576    bufLen = addKeyValueInt(&buf, bufLen, "ImageWidth", ImageInfo.Width);
577    if (bufLen == 0) return NULL;
578
579    bufLen = addKeyValueInt(&buf, bufLen, "ImageLength", ImageInfo.Height);
580    if (bufLen == 0) return NULL;
581
582    bufLen = addKeyValueInt(&buf, bufLen, "Orientation", ImageInfo.Orientation);
583    if (bufLen == 0) return NULL;
584
585    if (ImageInfo.FlashUsed >= 0) {
586        bufLen = addKeyValueInt(&buf, bufLen, "Flash", ImageInfo.FlashUsed);
587        if (bufLen == 0) return NULL;
588    }
589
590    if (ImageInfo.FocalLength.num != 0 && ImageInfo.FocalLength.denom != 0) {
591        bufLen = addKeyValueRational(&buf, bufLen, "FocalLength", ImageInfo.FocalLength);
592        if (bufLen == 0) return NULL;
593    }
594
595    if (ImageInfo.DigitalZoomRatio > 1.0){
596        // Digital zoom used.  Shame on you!
597        bufLen = addKeyValueDouble(&buf, bufLen, "DigitalZoomRatio", ImageInfo.DigitalZoomRatio, "%1.3f");
598        if (bufLen == 0) return NULL;
599    }
600
601    if (ImageInfo.ExposureTime){
602        const char* format;
603        if (ImageInfo.ExposureTime < 0.010){
604            format = "%6.4f";
605        } else {
606            format = "%5.3f";
607        }
608
609        bufLen = addKeyValueDouble(&buf, bufLen, "ExposureTime", (double)ImageInfo.ExposureTime, format);
610        if (bufLen == 0) return NULL;
611    }
612
613    if (ImageInfo.ApertureFNumber){
614        bufLen = addKeyValueDouble(&buf, bufLen, "FNumber", (double)ImageInfo.ApertureFNumber, "%3.1f");
615        if (bufLen == 0) return NULL;
616    }
617
618    if (ImageInfo.Distance){
619        bufLen = addKeyValueDouble(&buf, bufLen, "SubjectDistance", (double)ImageInfo.Distance, "%4.2f");
620        if (bufLen == 0) return NULL;
621    }
622
623    if (ImageInfo.ISOequivalent){
624        bufLen = addKeyValueInt(&buf, bufLen, "ISOSpeedRatings", ImageInfo.ISOequivalent);
625        if (bufLen == 0) return NULL;
626    }
627
628    if (ImageInfo.ExposureBias){
629        // If exposure bias was specified, but set to zero, presumably its no bias at all,
630        // so only show it if its nonzero.
631        bufLen = addKeyValueDouble(&buf, bufLen, "ExposureBiasValue", (double)ImageInfo.ExposureBias, "%4.2f");
632        if (bufLen == 0) return NULL;
633    }
634
635    if (ImageInfo.Whitebalance >= 0) {
636        bufLen = addKeyValueInt(&buf, bufLen, "WhiteBalance", ImageInfo.Whitebalance);
637        if (bufLen == 0) return NULL;
638    }
639
640    bufLen = addKeyValueInt(&buf, bufLen, "LightSource", ImageInfo.LightSource);
641    if (bufLen == 0) return NULL;
642
643
644    if (ImageInfo.MeteringMode) {
645        bufLen = addKeyValueInt(&buf, bufLen, "MeteringMode", ImageInfo.MeteringMode);
646        if (bufLen == 0) return NULL;
647    }
648
649    if (ImageInfo.ExposureProgram) {
650        bufLen = addKeyValueInt(&buf, bufLen, "ExposureProgram", ImageInfo.ExposureProgram);
651        if (bufLen == 0) return NULL;
652    }
653
654    if (ImageInfo.ExposureMode) {
655        bufLen = addKeyValueInt(&buf, bufLen, "ExposureMode", ImageInfo.ExposureMode);
656        if (bufLen == 0) return NULL;
657    }
658
659    if (ImageInfo.GpsInfoPresent) {
660        if (ImageInfo.GpsLatRaw[0]) {
661            bufLen = addKeyValueString(&buf, bufLen, "GPSLatitude", ImageInfo.GpsLatRaw);
662            if (bufLen == 0) return NULL;
663        }
664        if (ImageInfo.GpsLatRef[0]) {
665            bufLen = addKeyValueString(&buf, bufLen, "GPSLatitudeRef", ImageInfo.GpsLatRef);
666            if (bufLen == 0) return NULL;
667        }
668        if (ImageInfo.GpsLongRaw[0]) {
669            bufLen = addKeyValueString(&buf, bufLen, "GPSLongitude", ImageInfo.GpsLongRaw);
670            if (bufLen == 0) return NULL;
671        }
672        if (ImageInfo.GpsLongRef[0]) {
673            bufLen = addKeyValueString(&buf, bufLen, "GPSLongitudeRef", ImageInfo.GpsLongRef);
674            if (bufLen == 0) return NULL;
675        }
676        if (ImageInfo.GpsAlt[0]) {
677            bufLen = addKeyValueRational(&buf, bufLen, "GPSAltitude", ImageInfo.GpsAltRaw);
678            bufLen = addKeyValueInt(&buf, bufLen, "GPSAltitudeRef", ImageInfo.GpsAltRef);
679            if (bufLen == 0) return NULL;
680        }
681        if (ImageInfo.GpsDateStamp[0]) {
682            bufLen = addKeyValueString(&buf, bufLen, "GPSDateStamp", ImageInfo.GpsDateStamp);
683            if (bufLen == 0) return NULL;
684        }
685        if (ImageInfo.GpsTimeStamp[0]) {
686            bufLen = addKeyValueString(&buf, bufLen, "GPSTimeStamp", ImageInfo.GpsTimeStamp);
687            if (bufLen == 0) return NULL;
688        }
689        if (ImageInfo.GpsProcessingMethod[0]) {
690            bufLen = addKeyValueString(&buf, bufLen, "GPSProcessingMethod", ImageInfo.GpsProcessingMethod);
691            if (bufLen == 0) return NULL;
692        }
693    }
694
695    if (ImageInfo.Comments[0]) {
696        bufLen = addKeyValueString(&buf, bufLen, "UserComment", ImageInfo.Comments);
697        if (bufLen == 0) return NULL;
698    }
699
700    // put the attribute count at the beginnnig of the string
701    int finalBufLen = strlen(buf) + 20;
702    char* finalResult = malloc(finalBufLen);
703    if (finalResult == NULL) {
704        free(buf);
705        return NULL;
706    }
707    snprintf(finalResult, finalBufLen, "%d %s", attributeCount, buf);
708    int k;
709    for (k = 0; k < finalBufLen; k++)
710        if (!isascii(finalResult[k]))
711            finalResult[k] = '?';
712    free(buf);
713
714#ifdef SUPERDEBUG
715    ALOGE("*********Returning result \"%s\"", finalResult);
716#endif
717    jstring result = ((*env)->NewStringUTF(env, finalResult));
718    free(finalResult);
719    DiscardData();
720    return result;
721}
722
723static const char *classPathName = "android/media/ExifInterface";
724
725static JNINativeMethod methods[] = {
726  {"saveAttributesNative", "(Ljava/lang/String;Ljava/lang/String;)V", (void*)saveAttributes },
727  {"getAttributesNative", "(Ljava/lang/String;)Ljava/lang/String;", (void*)getAttributes },
728  {"appendThumbnailNative", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)appendThumbnail },
729  {"commitChangesNative", "(Ljava/lang/String;)V", (void*)commitChanges },
730  {"getThumbnailNative", "(Ljava/lang/String;)[B", (void*)getThumbnail },
731};
732
733/*
734 * Register several native methods for one class.
735 */
736static int registerNativeMethods(JNIEnv* env, const char* className,
737    JNINativeMethod* gMethods, int numMethods)
738{
739    jclass clazz;
740
741    clazz = (*env)->FindClass(env, className);
742    if (clazz == NULL) {
743        fprintf(stderr,
744            "Native registration unable to find class '%s'\n", className);
745        return JNI_FALSE;
746    }
747    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
748        fprintf(stderr, "RegisterNatives failed for '%s'\n", className);
749        return JNI_FALSE;
750    }
751
752    return JNI_TRUE;
753}
754
755/*
756 * Register native methods for all classes we know about.
757 */
758static int registerNatives(JNIEnv* env)
759{
760    return jniRegisterNativeMethods(env, classPathName,
761                                    methods, NELEM(methods));
762}
763
764/*
765 * Set some test stuff up.
766 *
767 * Returns the JNI version on success, -1 on failure.
768 */
769__attribute__ ((visibility("default"))) jint JNI_OnLoad(JavaVM* vm, void* reserved)
770{
771    JNIEnv* env = NULL;
772    jint result = -1;
773
774    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
775        fprintf(stderr, "ERROR: GetEnv failed\n");
776        goto bail;
777    }
778    assert(env != NULL);
779
780    printf("In mgmain JNI_OnLoad\n");
781
782    if (registerNatives(env) < 0) {
783        fprintf(stderr, "ERROR: Exif native registration failed\n");
784        goto bail;
785    }
786
787    /* success -- return valid version number */
788    result = JNI_VERSION_1_4;
789
790bail:
791    return result;
792}
793