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 jlongArray getThumbnailRange(JNIEnv *env, jobject jobj, jstring jfilename) {
479    jlongArray resultArray = NULL;
480    const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
481    if (filename) {
482        loadExifInfo(filename, FALSE);
483        Section_t* ExifSection = FindSection(M_EXIF);
484        if (ExifSection == NULL || ImageInfo.ThumbnailSize == 0) {
485            goto done;
486        }
487
488        jlong result[2];
489        result[0] = ExifSection->Offset + ImageInfo.ThumbnailOffset + 8;
490        result[1] = ImageInfo.ThumbnailSize;
491
492        resultArray = (*env)->NewLongArray(env, 2);
493        if (resultArray == NULL) {
494            goto done;
495        }
496
497        (*env)->SetLongArrayRegion(env, resultArray, 0, 2, result);
498    }
499done:
500    if (filename) {
501        (*env)->ReleaseStringUTFChars(env, jfilename, filename);
502    }
503    DiscardData();
504    return resultArray;
505}
506
507static int attributeCount;      // keep track of how many attributes we've added
508
509// returns new buffer length
510static int addKeyValueString(char** buf, int bufLen, const char* key, const char* value) {
511    // Appends to buf like this: "ImageLength=4 1024"
512
513    char valueLen[15];
514    snprintf(valueLen, 15, "=%d ", (int)strlen(value));
515
516    // check to see if buf has enough room to append
517    int len = strlen(key) + strlen(valueLen) + strlen(value);
518    int newLen = strlen(*buf) + len;
519    if (newLen >= bufLen) {
520#ifdef REALLOCTEST
521        bufLen = newLen + 5;
522        ALOGE("reallocing to %d", bufLen);
523#else
524        bufLen = newLen + 500;
525#endif
526        *buf = realloc(*buf, bufLen);
527        if (*buf == NULL) {
528            return 0;
529        }
530    }
531    // append the new attribute and value
532    snprintf(*buf + strlen(*buf), bufLen, "%s%s%s", key, valueLen, value);
533#ifdef SUPERDEBUG
534    ALOGE("buf %s", *buf);
535#endif
536    ++attributeCount;
537    return bufLen;
538}
539
540// returns new buffer length
541static int addKeyValueInt(char** buf, int bufLen, const char* key, int value) {
542    char valueStr[20];
543    snprintf(valueStr, 20, "%d", value);
544
545    return addKeyValueString(buf, bufLen, key, valueStr);
546}
547
548// returns new buffer length
549static int addKeyValueDouble(char** buf, int bufLen, const char* key, double value, const char* format) {
550    char valueStr[30];
551    snprintf(valueStr, 30, format, value);
552
553    return addKeyValueString(buf, bufLen, key, valueStr);
554}
555
556// Returns new buffer length. Rational value will be appended as "numerator/denominator".
557static int addKeyValueRational(char** buf, int bufLen, const char* key, rat_t value) {
558    char valueStr[25];
559    snprintf(valueStr, sizeof(valueStr), "%u/%u", value.num, value.denom);
560    return addKeyValueString(buf, bufLen, key, valueStr);
561}
562
563static jstring getAttributes(JNIEnv *env, jobject jobj, jstring jfilename)
564{
565#ifdef SUPERDEBUG
566    ALOGE("******************************** getAttributes\n");
567#endif
568    const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
569    loadExifInfo(filename, FALSE);
570#ifdef SUPERDEBUG
571    ShowImageInfo(TRUE);
572#endif
573    (*env)->ReleaseStringUTFChars(env, jfilename, filename);
574
575    attributeCount = 0;
576#ifdef REALLOCTEST
577    int bufLen = 5;
578#else
579    int bufLen = 1000;
580#endif
581    char* buf = malloc(bufLen);
582    if (buf == NULL) {
583        return NULL;
584    }
585    *buf = 0;   // start the string out at zero length
586
587    // save a fake "hasThumbnail" tag to pass to the java ExifInterface
588    bufLen = addKeyValueString(&buf, bufLen, "hasThumbnail",
589        ImageInfo.ThumbnailOffset == 0 || ImageInfo.ThumbnailAtEnd == FALSE || ImageInfo.ThumbnailSize == 0 ?
590            "false" : "true");
591    if (bufLen == 0) return NULL;
592
593    if (ImageInfo.CameraMake[0]) {
594        bufLen = addKeyValueString(&buf, bufLen, "Make", ImageInfo.CameraMake);
595        if (bufLen == 0) return NULL;
596    }
597    if (ImageInfo.CameraModel[0]) {
598        bufLen = addKeyValueString(&buf, bufLen, "Model", ImageInfo.CameraModel);
599        if (bufLen == 0) return NULL;
600    }
601    if (ImageInfo.DateTime[0]) {
602        bufLen = addKeyValueString(&buf, bufLen, "DateTime", ImageInfo.DateTime);
603        if (bufLen == 0) return NULL;
604    }
605    if (ImageInfo.DigitizedTime[0]) {
606        bufLen = addKeyValueString(&buf, bufLen, "DateTimeDigitized", ImageInfo.DigitizedTime);
607        if (bufLen == 0) return NULL;
608    }
609    if (ImageInfo.SubSecTime[0]) {
610        bufLen = addKeyValueString(&buf, bufLen, "SubSecTime", ImageInfo.SubSecTime);
611        if (bufLen == 0) return NULL;
612    }
613    if (ImageInfo.SubSecTimeOrig[0]) {
614        bufLen = addKeyValueString(&buf, bufLen, "SubSecTimeOriginal", ImageInfo.SubSecTimeOrig);
615        if (bufLen == 0) return NULL;
616    }
617    if (ImageInfo.SubSecTimeDig[0]) {
618        bufLen = addKeyValueString(&buf, bufLen, "SubSecTimeDigitized", ImageInfo.SubSecTimeDig);
619        if (bufLen == 0) return NULL;
620    }
621
622    bufLen = addKeyValueInt(&buf, bufLen, "ImageWidth", ImageInfo.Width);
623    if (bufLen == 0) return NULL;
624
625    bufLen = addKeyValueInt(&buf, bufLen, "ImageLength", ImageInfo.Height);
626    if (bufLen == 0) return NULL;
627
628    bufLen = addKeyValueInt(&buf, bufLen, "Orientation", ImageInfo.Orientation);
629    if (bufLen == 0) return NULL;
630
631    if (ImageInfo.FlashUsed >= 0) {
632        bufLen = addKeyValueInt(&buf, bufLen, "Flash", ImageInfo.FlashUsed);
633        if (bufLen == 0) return NULL;
634    }
635
636    if (ImageInfo.FocalLength.num != 0 && ImageInfo.FocalLength.denom != 0) {
637        bufLen = addKeyValueRational(&buf, bufLen, "FocalLength", ImageInfo.FocalLength);
638        if (bufLen == 0) return NULL;
639    }
640
641    if (ImageInfo.DigitalZoomRatio > 1.0){
642        // Digital zoom used.  Shame on you!
643        bufLen = addKeyValueDouble(&buf, bufLen, "DigitalZoomRatio", ImageInfo.DigitalZoomRatio, "%1.3f");
644        if (bufLen == 0) return NULL;
645    }
646
647    if (ImageInfo.ExposureTime){
648        const char* format;
649        if (ImageInfo.ExposureTime < 0.010){
650            format = "%6.4f";
651        } else {
652            format = "%5.3f";
653        }
654
655        bufLen = addKeyValueDouble(&buf, bufLen, "ExposureTime", (double)ImageInfo.ExposureTime, format);
656        if (bufLen == 0) return NULL;
657    }
658
659    if (ImageInfo.ApertureFNumber){
660        bufLen = addKeyValueDouble(&buf, bufLen, "FNumber", (double)ImageInfo.ApertureFNumber, "%3.1f");
661        if (bufLen == 0) return NULL;
662    }
663
664    if (ImageInfo.Distance){
665        bufLen = addKeyValueDouble(&buf, bufLen, "SubjectDistance", (double)ImageInfo.Distance, "%4.2f");
666        if (bufLen == 0) return NULL;
667    }
668
669    if (ImageInfo.ISOequivalent){
670        bufLen = addKeyValueInt(&buf, bufLen, "ISOSpeedRatings", ImageInfo.ISOequivalent);
671        if (bufLen == 0) return NULL;
672    }
673
674    if (ImageInfo.ExposureBias){
675        // If exposure bias was specified, but set to zero, presumably its no bias at all,
676        // so only show it if its nonzero.
677        bufLen = addKeyValueDouble(&buf, bufLen, "ExposureBiasValue", (double)ImageInfo.ExposureBias, "%4.2f");
678        if (bufLen == 0) return NULL;
679    }
680
681    if (ImageInfo.Whitebalance >= 0) {
682        bufLen = addKeyValueInt(&buf, bufLen, "WhiteBalance", ImageInfo.Whitebalance);
683        if (bufLen == 0) return NULL;
684    }
685
686    bufLen = addKeyValueInt(&buf, bufLen, "LightSource", ImageInfo.LightSource);
687    if (bufLen == 0) return NULL;
688
689
690    if (ImageInfo.MeteringMode) {
691        bufLen = addKeyValueInt(&buf, bufLen, "MeteringMode", ImageInfo.MeteringMode);
692        if (bufLen == 0) return NULL;
693    }
694
695    if (ImageInfo.ExposureProgram) {
696        bufLen = addKeyValueInt(&buf, bufLen, "ExposureProgram", ImageInfo.ExposureProgram);
697        if (bufLen == 0) return NULL;
698    }
699
700    if (ImageInfo.ExposureMode) {
701        bufLen = addKeyValueInt(&buf, bufLen, "ExposureMode", ImageInfo.ExposureMode);
702        if (bufLen == 0) return NULL;
703    }
704
705    if (ImageInfo.GpsInfoPresent) {
706        if (ImageInfo.GpsLatRaw[0]) {
707            bufLen = addKeyValueString(&buf, bufLen, "GPSLatitude", ImageInfo.GpsLatRaw);
708            if (bufLen == 0) return NULL;
709        }
710        if (ImageInfo.GpsLatRef[0]) {
711            bufLen = addKeyValueString(&buf, bufLen, "GPSLatitudeRef", ImageInfo.GpsLatRef);
712            if (bufLen == 0) return NULL;
713        }
714        if (ImageInfo.GpsLongRaw[0]) {
715            bufLen = addKeyValueString(&buf, bufLen, "GPSLongitude", ImageInfo.GpsLongRaw);
716            if (bufLen == 0) return NULL;
717        }
718        if (ImageInfo.GpsLongRef[0]) {
719            bufLen = addKeyValueString(&buf, bufLen, "GPSLongitudeRef", ImageInfo.GpsLongRef);
720            if (bufLen == 0) return NULL;
721        }
722        if (ImageInfo.GpsAlt[0]) {
723            bufLen = addKeyValueRational(&buf, bufLen, "GPSAltitude", ImageInfo.GpsAltRaw);
724            bufLen = addKeyValueInt(&buf, bufLen, "GPSAltitudeRef", ImageInfo.GpsAltRef);
725            if (bufLen == 0) return NULL;
726        }
727        if (ImageInfo.GpsDateStamp[0]) {
728            bufLen = addKeyValueString(&buf, bufLen, "GPSDateStamp", ImageInfo.GpsDateStamp);
729            if (bufLen == 0) return NULL;
730        }
731        if (ImageInfo.GpsTimeStamp[0]) {
732            bufLen = addKeyValueString(&buf, bufLen, "GPSTimeStamp", ImageInfo.GpsTimeStamp);
733            if (bufLen == 0) return NULL;
734        }
735        if (ImageInfo.GpsProcessingMethod[0]) {
736            bufLen = addKeyValueString(&buf, bufLen, "GPSProcessingMethod", ImageInfo.GpsProcessingMethod);
737            if (bufLen == 0) return NULL;
738        }
739    }
740
741    if (ImageInfo.Comments[0]) {
742        bufLen = addKeyValueString(&buf, bufLen, "UserComment", ImageInfo.Comments);
743        if (bufLen == 0) return NULL;
744    }
745
746    // put the attribute count at the beginnnig of the string
747    int finalBufLen = strlen(buf) + 20;
748    char* finalResult = malloc(finalBufLen);
749    if (finalResult == NULL) {
750        free(buf);
751        return NULL;
752    }
753    snprintf(finalResult, finalBufLen, "%d %s", attributeCount, buf);
754    int k;
755    for (k = 0; k < finalBufLen; k++)
756        if (!isascii(finalResult[k]))
757            finalResult[k] = '?';
758    free(buf);
759
760#ifdef SUPERDEBUG
761    ALOGE("*********Returning result \"%s\"", finalResult);
762#endif
763    jstring result = ((*env)->NewStringUTF(env, finalResult));
764    free(finalResult);
765    DiscardData();
766    return result;
767}
768
769static const char *classPathName = "android/media/ExifInterface";
770
771static JNINativeMethod methods[] = {
772  {"saveAttributesNative", "(Ljava/lang/String;Ljava/lang/String;)V", (void*)saveAttributes },
773  {"getAttributesNative", "(Ljava/lang/String;)Ljava/lang/String;", (void*)getAttributes },
774  {"appendThumbnailNative", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)appendThumbnail },
775  {"commitChangesNative", "(Ljava/lang/String;)V", (void*)commitChanges },
776  {"getThumbnailNative", "(Ljava/lang/String;)[B", (void*)getThumbnail },
777  {"getThumbnailRangeNative", "(Ljava/lang/String;)[J", (void*)getThumbnailRange },
778};
779
780/*
781 * Register several native methods for one class.
782 */
783static int registerNativeMethods(JNIEnv* env, const char* className,
784    JNINativeMethod* gMethods, int numMethods)
785{
786    jclass clazz;
787
788    clazz = (*env)->FindClass(env, className);
789    if (clazz == NULL) {
790        fprintf(stderr,
791            "Native registration unable to find class '%s'\n", className);
792        return JNI_FALSE;
793    }
794    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
795        fprintf(stderr, "RegisterNatives failed for '%s'\n", className);
796        return JNI_FALSE;
797    }
798
799    return JNI_TRUE;
800}
801
802/*
803 * Register native methods for all classes we know about.
804 */
805static int registerNatives(JNIEnv* env)
806{
807    return jniRegisterNativeMethods(env, classPathName,
808                                    methods, NELEM(methods));
809}
810
811/*
812 * Set some test stuff up.
813 *
814 * Returns the JNI version on success, -1 on failure.
815 */
816__attribute__ ((visibility("default"))) jint JNI_OnLoad(JavaVM* vm, void* reserved)
817{
818    JNIEnv* env = NULL;
819    jint result = -1;
820
821    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
822        fprintf(stderr, "ERROR: GetEnv failed\n");
823        goto bail;
824    }
825    assert(env != NULL);
826
827    printf("In mgmain JNI_OnLoad\n");
828
829    if (registerNatives(env) < 0) {
830        fprintf(stderr, "ERROR: Exif native registration failed\n");
831        goto bail;
832    }
833
834    /* success -- return valid version number */
835    result = JNI_VERSION_1_4;
836
837bail:
838    return result;
839}
840