120b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync/*
220b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync * Copyright (C) 2007 The Android Open Source Project
320b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync *
420b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync * Licensed under the Apache License, Version 2.0 (the "License");
520b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync * you may not use this file except in compliance with the License.
620b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync * You may obtain a copy of the License at
720b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync *
820b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync *      http://www.apache.org/licenses/LICENSE-2.0
920b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync *
1020b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync * Unless required by applicable law or agreed to in writing, software
1120b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync * distributed under the License is distributed on an "AS IS" BASIS,
1220b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1320b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync * See the License for the specific language governing permissions and
1420b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync * limitations under the License.
1520b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync */
1620b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
1720b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo syncpackage android.media;
1820b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
19700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Changimport java.io.IOException;
20099397cbd07c8c991f3126d0d0ac64bb6b3c0b47Chih-Chung Changimport java.text.ParsePosition;
21099397cbd07c8c991f3126d0d0ac64bb6b3c0b47Chih-Chung Changimport java.text.SimpleDateFormat;
22099397cbd07c8c991f3126d0d0ac64bb6b3c0b47Chih-Chung Changimport java.util.Date;
2320b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo syncimport java.util.HashMap;
2420b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo syncimport java.util.Map;
25d5fa58cf3d71f2cf94299584050b2698fa3753ecRay Chenimport java.util.TimeZone;
2620b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
2720b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync/**
28700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang * This is a class for reading and writing Exif tags in a JPEG file.
2920b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync */
3020b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo syncpublic class ExifInterface {
3120b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    // The Exif tag names
325d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li    /** Type is int. */
3320b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    public static final String TAG_ORIENTATION = "Orientation";
345d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li    /** Type is String. */
35099397cbd07c8c991f3126d0d0ac64bb6b3c0b47Chih-Chung Chang    public static final String TAG_DATETIME = "DateTime";
365d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li    /** Type is String. */
3720b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    public static final String TAG_MAKE = "Make";
385d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li    /** Type is String. */
3920b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    public static final String TAG_MODEL = "Model";
405d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li    /** Type is int. */
4120b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    public static final String TAG_FLASH = "Flash";
425d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li    /** Type is int. */
4320b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    public static final String TAG_IMAGE_WIDTH = "ImageWidth";
445d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li    /** Type is int. */
4520b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    public static final String TAG_IMAGE_LENGTH = "ImageLength";
465d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li    /** String. Format is "num1/denom1,num2/denom2,num3/denom3". */
4720b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    public static final String TAG_GPS_LATITUDE = "GPSLatitude";
485d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li    /** String. Format is "num1/denom1,num2/denom2,num3/denom3". */
4920b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    public static final String TAG_GPS_LONGITUDE = "GPSLongitude";
505d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li    /** Type is String. */
5120b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    public static final String TAG_GPS_LATITUDE_REF = "GPSLatitudeRef";
525d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li    /** Type is String. */
5320b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    public static final String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef";
544414eb4b2955be9eb62d1ddf379d1a7ae1822887Ray Chen    /** Type is String. */
554414eb4b2955be9eb62d1ddf379d1a7ae1822887Ray Chen    public static final String TAG_EXPOSURE_TIME = "ExposureTime";
564414eb4b2955be9eb62d1ddf379d1a7ae1822887Ray Chen    /** Type is String. */
574414eb4b2955be9eb62d1ddf379d1a7ae1822887Ray Chen    public static final String TAG_APERTURE = "FNumber";
584414eb4b2955be9eb62d1ddf379d1a7ae1822887Ray Chen    /** Type is String. */
594414eb4b2955be9eb62d1ddf379d1a7ae1822887Ray Chen    public static final String TAG_ISO = "ISOSpeedRatings";
60c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li
61c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li    /**
62c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li     * The altitude (in meters) based on the reference in TAG_GPS_ALTITUDE_REF.
63c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li     * Type is rational.
64c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li     */
65c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li    public static final String TAG_GPS_ALTITUDE = "GPSAltitude";
66c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li
67c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li    /**
68c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li     * 0 if the altitude is above sea level. 1 if the altitude is below sea
69c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li     * level. Type is int.
70c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li     */
71c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li    public static final String TAG_GPS_ALTITUDE_REF = "GPSAltitudeRef";
72c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li
735d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li    /** Type is String. */
743a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen    public static final String TAG_GPS_TIMESTAMP = "GPSTimeStamp";
755d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li    /** Type is String. */
763a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen    public static final String TAG_GPS_DATESTAMP = "GPSDateStamp";
775d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li    /** Type is int. */
7820b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    public static final String TAG_WHITE_BALANCE = "WhiteBalance";
795d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li    /** Type is rational. */
805d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li    public static final String TAG_FOCAL_LENGTH = "FocalLength";
81e208377fbab6b90f41e68699700942a81f4caaebRay Chen    /** Type is String. Name of GPS processing method used for location finding. */
82e208377fbab6b90f41e68699700942a81f4caaebRay Chen    public static final String TAG_GPS_PROCESSING_METHOD = "GPSProcessingMethod";
8320b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
84700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    // Constants used for the Orientation Exif tag.
85700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    public static final int ORIENTATION_UNDEFINED = 0;
86700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    public static final int ORIENTATION_NORMAL = 1;
87700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    public static final int ORIENTATION_FLIP_HORIZONTAL = 2;  // left right reversed mirror
88700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    public static final int ORIENTATION_ROTATE_180 = 3;
89700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    public static final int ORIENTATION_FLIP_VERTICAL = 4;  // upside down mirror
90700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    public static final int ORIENTATION_TRANSPOSE = 5;  // flipped about top-left <--> bottom-right axis
91700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    public static final int ORIENTATION_ROTATE_90 = 6;  // rotate 90 cw to right it
92700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    public static final int ORIENTATION_TRANSVERSE = 7;  // flipped about top-right <--> bottom-left axis
93700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    public static final int ORIENTATION_ROTATE_270 = 8;  // rotate 270 to right it
94700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang
95700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    // Constants used for white balance
96700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    public static final int WHITEBALANCE_AUTO = 0;
97700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    public static final int WHITEBALANCE_MANUAL = 1;
98d5fa58cf3d71f2cf94299584050b2698fa3753ecRay Chen    private static SimpleDateFormat sFormatter;
9920b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
10020b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    static {
101934349e8ad37c1ad251d6992e0751635f9ab3f28Zhijun He        System.loadLibrary("jhead_jni");
102d5fa58cf3d71f2cf94299584050b2698fa3753ecRay Chen        sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
1033a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen        sFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
10420b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    }
10520b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
106700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    private String mFilename;
107700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    private HashMap<String, String> mAttributes;
108872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang    private boolean mHasThumbnail;
10920b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
110700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    // Because the underlying implementation (jhead) uses static variables,
111700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    // there can only be one user at a time for the native functions (and
112700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    // they cannot keep state in the native code across function calls). We
113872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang    // use sLock to serialize the accesses.
11430c918ce7fbe171944b28fc91b3f22b3d631872dGlenn Kasten    private static final Object sLock = new Object();
11520b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
11620b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    /**
117700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     * Reads Exif tags from the specified JPEG file.
11820b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync     */
119700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    public ExifInterface(String filename) throws IOException {
120c9b6be7c402bd349f8d9322730256f593053dc52Dave Burke        if (filename == null) {
121c9b6be7c402bd349f8d9322730256f593053dc52Dave Burke            throw new IllegalArgumentException("filename cannot be null");
122c9b6be7c402bd349f8d9322730256f593053dc52Dave Burke        }
123700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        mFilename = filename;
124700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        loadAttributes();
12520b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    }
12620b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
127700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    /**
128700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     * Returns the value of the specified tag or {@code null} if there
129872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang     * is no such tag in the JPEG file.
130700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     *
131700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     * @param tag the name of the tag.
132700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     */
133700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    public String getAttribute(String tag) {
134700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        return mAttributes.get(tag);
13520b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    }
13620b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
13720b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    /**
138872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang     * Returns the integer value of the specified tag. If there is no such tag
139872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang     * in the JPEG file or the value cannot be parsed as integer, return
14029e4a3c566f435c32f0b95e4ac8e8b33cac6fabaDianne Hackborn     * <var>defaultValue</var>.
141872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang     *
142872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang     * @param tag the name of the tag.
143872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang     * @param defaultValue the value to return if the tag is not available.
144872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang     */
145872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang    public int getAttributeInt(String tag, int defaultValue) {
146872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang        String value = mAttributes.get(tag);
147872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang        if (value == null) return defaultValue;
148872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang        try {
149872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang            return Integer.valueOf(value);
150872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang        } catch (NumberFormatException ex) {
151872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang            return defaultValue;
152872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang        }
153872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang    }
154872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang
155872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang    /**
1565d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li     * Returns the double value of the specified rational tag. If there is no
1575d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li     * such tag in the JPEG file or the value cannot be parsed as double, return
1585d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li     * <var>defaultValue</var>.
1595d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li     *
1605d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li     * @param tag the name of the tag.
1615d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li     * @param defaultValue the value to return if the tag is not available.
1625d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li     */
1635d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li    public double getAttributeDouble(String tag, double defaultValue) {
1645d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li        String value = mAttributes.get(tag);
1655d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li        if (value == null) return defaultValue;
1665d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li        try {
1675d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li            int index = value.indexOf("/");
1685d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li            if (index == -1) return defaultValue;
1695d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li            double denom = Double.parseDouble(value.substring(index + 1));
1705d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li            if (denom == 0) return defaultValue;
1715d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li            double num = Double.parseDouble(value.substring(0, index));
1725d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li            return num / denom;
1735d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li        } catch (NumberFormatException ex) {
1745d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li            return defaultValue;
1755d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li        }
1765d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li    }
1775d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li
1785d9661be8eaa2a4702a4d3c0fa0fd07a8638b503Wu-cheng Li    /**
179700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     * Set the value of the specified tag.
180700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     *
181700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     * @param tag the name of the tag.
182700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     * @param value the value of the tag.
18320b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync     */
184700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    public void setAttribute(String tag, String value) {
185700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        mAttributes.put(tag, value);
18620b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    }
18720b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
18820b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    /**
189700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     * Initialize mAttributes with the attributes from the file mFilename.
190700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     *
191700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     * mAttributes is a HashMap which stores the Exif attributes of the file.
192700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     * The key is the standard tag name and the value is the tag's value: e.g.
193700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     * Model -> Nikon. Numeric values are stored as strings.
194700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     *
195700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     * This function also initialize mHasThumbnail to indicate whether the
196700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     * file has a thumbnail inside.
19720b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync     */
198872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang    private void loadAttributes() throws IOException {
19920b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync        // format of string passed from native C code:
20020b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync        // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
20120b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync        // example:
20220b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync        // "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
203700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        mAttributes = new HashMap<String, String>();
20420b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
205700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        String attrStr;
206700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        synchronized (sLock) {
207700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang            attrStr = getAttributesNative(mFilename);
208700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        }
20920b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
21020b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync        // get count
21120b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync        int ptr = attrStr.indexOf(' ');
21220b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync        int count = Integer.parseInt(attrStr.substring(0, ptr));
21320b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync        // skip past the space between item count and the rest of the attributes
21420b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync        ++ptr;
21520b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
21620b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync        for (int i = 0; i < count; i++) {
21720b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            // extract the attribute name
21820b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            int equalPos = attrStr.indexOf('=', ptr);
21920b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            String attrName = attrStr.substring(ptr, equalPos);
22020b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            ptr = equalPos + 1;     // skip past =
22120b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
22220b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            // extract the attribute value length
22320b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            int lenPos = attrStr.indexOf(' ', ptr);
22420b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            int attrLen = Integer.parseInt(attrStr.substring(ptr, lenPos));
22520b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            ptr = lenPos + 1;       // skip pas the space
22620b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
22720b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            // extract the attribute value
22820b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            String attrValue = attrStr.substring(ptr, ptr + attrLen);
22920b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            ptr += attrLen;
23020b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
23120b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            if (attrName.equals("hasThumbnail")) {
23220b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync                mHasThumbnail = attrValue.equalsIgnoreCase("true");
23320b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            } else {
234700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang                mAttributes.put(attrName, attrValue);
23520b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            }
23620b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync        }
23720b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    }
23820b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
23920b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    /**
240700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     * Save the tag data into the JPEG file. This is expensive because it involves
241700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     * copying all the JPG data from one file to another and deleting the old file
242872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang     * and renaming the other. It's best to use {@link #setAttribute(String,String)}
243872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang     * to set all attributes to write and make a single call rather than multiple
244872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang     * calls for each attribute.
24520b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync     */
246700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    public void saveAttributes() throws IOException {
247700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        // format of string passed to native C code:
248700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
249700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        // example:
250700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        // "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
251700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        StringBuilder sb = new StringBuilder();
252700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        int size = mAttributes.size();
253700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        if (mAttributes.containsKey("hasThumbnail")) {
254700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang            --size;
255700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        }
256700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        sb.append(size + " ");
257700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        for (Map.Entry<String, String> iter : mAttributes.entrySet()) {
258700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang            String key = iter.getKey();
259700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang            if (key.equals("hasThumbnail")) {
260700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang                // this is a fake attribute not saved as an exif tag
261700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang                continue;
262700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang            }
263700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang            String val = iter.getValue();
264700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang            sb.append(key + "=");
265700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang            sb.append(val.length() + " ");
266700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang            sb.append(val);
267700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        }
268700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        String s = sb.toString();
269700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        synchronized (sLock) {
270700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang            saveAttributesNative(mFilename, s);
271700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang            commitChangesNative(mFilename);
272700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        }
273700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    }
274700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang
275700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    /**
276700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     * Returns true if the JPEG file has a thumbnail.
277700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     */
278700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    public boolean hasThumbnail() {
279700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        return mHasThumbnail;
280700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    }
281700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang
282700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    /**
283700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     * Returns the thumbnail inside the JPEG file, or {@code null} if there is no thumbnail.
284872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang     * The returned data is in JPEG format and can be decoded using
285872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang     * {@link android.graphics.BitmapFactory#decodeByteArray(byte[],int,int)}
286700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     */
287700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    public byte[] getThumbnail() {
288700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        synchronized (sLock) {
289700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang            return getThumbnailNative(mFilename);
290700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        }
291700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    }
292700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang
293700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    /**
2946398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkey     * Returns the offset and length of thumbnail inside the JPEG file, or
2956398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkey     * {@code null} if there is no thumbnail.
2966398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkey     *
2976398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkey     * @return two-element array, the offset in the first value, and length in
2986398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkey     *         the second, or {@code null} if no thumbnail was found.
2996398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkey     * @hide
3006398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkey     */
3016398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkey    public long[] getThumbnailRange() {
3026398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkey        synchronized (sLock) {
3036398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkey            return getThumbnailRangeNative(mFilename);
3046398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkey        }
3056398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkey    }
3066398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkey
3076398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkey    /**
308872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang     * Stores the latitude and longitude value in a float array. The first element is
309872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang     * the latitude, and the second element is the longitude. Returns false if the
310872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang     * Exif tags are not available.
31120b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync     */
312872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang    public boolean getLatLong(float output[]) {
313700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        String latValue = mAttributes.get(ExifInterface.TAG_GPS_LATITUDE);
314700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        String latRef = mAttributes.get(ExifInterface.TAG_GPS_LATITUDE_REF);
315700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        String lngValue = mAttributes.get(ExifInterface.TAG_GPS_LONGITUDE);
316700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        String lngRef = mAttributes.get(ExifInterface.TAG_GPS_LONGITUDE_REF);
31720b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
318872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang        if (latValue != null && latRef != null && lngValue != null && lngRef != null) {
319a113a075ca9afa14361806ea592c8f078b1636c5Oscar Rydhe            try {
320a113a075ca9afa14361806ea592c8f078b1636c5Oscar Rydhe                output[0] = convertRationalLatLonToFloat(latValue, latRef);
321a113a075ca9afa14361806ea592c8f078b1636c5Oscar Rydhe                output[1] = convertRationalLatLonToFloat(lngValue, lngRef);
322a113a075ca9afa14361806ea592c8f078b1636c5Oscar Rydhe                return true;
323a113a075ca9afa14361806ea592c8f078b1636c5Oscar Rydhe            } catch (IllegalArgumentException e) {
324a113a075ca9afa14361806ea592c8f078b1636c5Oscar Rydhe                // if values are not parseable
325a113a075ca9afa14361806ea592c8f078b1636c5Oscar Rydhe            }
32620b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync        }
327a113a075ca9afa14361806ea592c8f078b1636c5Oscar Rydhe
328a113a075ca9afa14361806ea592c8f078b1636c5Oscar Rydhe        return false;
32920b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    }
33020b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
331700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    /**
332c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li     * Return the altitude in meters. If the exif tag does not exist, return
333c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li     * <var>defaultValue</var>.
334c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li     *
335c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li     * @param defaultValue the value to return if the tag is not available.
336c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li     */
337c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li    public double getAltitude(double defaultValue) {
338c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li        double altitude = getAttributeDouble(TAG_GPS_ALTITUDE, -1);
339c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li        int ref = getAttributeInt(TAG_GPS_ALTITUDE_REF, -1);
340c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li
341c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li        if (altitude >= 0 && ref >= 0) {
342c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li            return (double) (altitude * ((ref == 1) ? -1 : 1));
343c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li        } else {
344c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li            return defaultValue;
345c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li        }
346c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li    }
347c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li
348c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li    /**
3493a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen     * Returns number of milliseconds since Jan. 1, 1970, midnight.
350700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     * Returns -1 if the date time information if not available.
351872a30ec723ebdd97de764406544516545d7c9d4Chih-Chung Chang     * @hide
352700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang     */
353700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    public long getDateTime() {
354700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang        String dateTimeString = mAttributes.get(TAG_DATETIME);
355099397cbd07c8c991f3126d0d0ac64bb6b3c0b47Chih-Chung Chang        if (dateTimeString == null) return -1;
356099397cbd07c8c991f3126d0d0ac64bb6b3c0b47Chih-Chung Chang
357099397cbd07c8c991f3126d0d0ac64bb6b3c0b47Chih-Chung Chang        ParsePosition pos = new ParsePosition(0);
358099397cbd07c8c991f3126d0d0ac64bb6b3c0b47Chih-Chung Chang        try {
3593a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen            Date datetime = sFormatter.parse(dateTimeString, pos);
3603a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen            if (datetime == null) return -1;
3613a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen            return datetime.getTime();
3623a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen        } catch (IllegalArgumentException ex) {
3633a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen            return -1;
3643a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen        }
3653a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen    }
3663a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen
3673a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen    /**
3683a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen     * Returns number of milliseconds since Jan. 1, 1970, midnight UTC.
3693a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen     * Returns -1 if the date time information if not available.
3703a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen     * @hide
3713a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen     */
3723a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen    public long getGpsDateTime() {
3733a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen        String date = mAttributes.get(TAG_GPS_DATESTAMP);
3743a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen        String time = mAttributes.get(TAG_GPS_TIMESTAMP);
3753a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen        if (date == null || time == null) return -1;
3763a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen
3773a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen        String dateTimeString = date + ' ' + time;
3783a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen        if (dateTimeString == null) return -1;
3793a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen
3803a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen        ParsePosition pos = new ParsePosition(0);
3813a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen        try {
3823a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen            Date datetime = sFormatter.parse(dateTimeString, pos);
3833a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen            if (datetime == null) return -1;
3843a8cab88e7747dc280ce85895af014f98e80a6ccRay Chen            return datetime.getTime();
385099397cbd07c8c991f3126d0d0ac64bb6b3c0b47Chih-Chung Chang        } catch (IllegalArgumentException ex) {
386099397cbd07c8c991f3126d0d0ac64bb6b3c0b47Chih-Chung Chang            return -1;
387099397cbd07c8c991f3126d0d0ac64bb6b3c0b47Chih-Chung Chang        }
388099397cbd07c8c991f3126d0d0ac64bb6b3c0b47Chih-Chung Chang    }
389099397cbd07c8c991f3126d0d0ac64bb6b3c0b47Chih-Chung Chang
390700beb484624a9a34649cb6ff088468e78b758ffChih-Chung Chang    private static float convertRationalLatLonToFloat(
39120b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            String rationalString, String ref) {
39220b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync        try {
39320b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            String [] parts = rationalString.split(",");
39420b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
39520b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            String [] pair;
39620b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            pair = parts[0].split("/");
397a113a075ca9afa14361806ea592c8f078b1636c5Oscar Rydhe            double degrees = Double.parseDouble(pair[0].trim())
398a113a075ca9afa14361806ea592c8f078b1636c5Oscar Rydhe                    / Double.parseDouble(pair[1].trim());
39920b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
40020b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            pair = parts[1].split("/");
401a113a075ca9afa14361806ea592c8f078b1636c5Oscar Rydhe            double minutes = Double.parseDouble(pair[0].trim())
402a113a075ca9afa14361806ea592c8f078b1636c5Oscar Rydhe                    / Double.parseDouble(pair[1].trim());
40320b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
40420b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            pair = parts[2].split("/");
405c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li            double seconds = Double.parseDouble(pair[0].trim())
406c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li                    / Double.parseDouble(pair[1].trim());
40720b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
408c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li            double result = degrees + (minutes / 60.0) + (seconds / 3600.0);
40920b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            if ((ref.equals("S") || ref.equals("W"))) {
410c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li                return (float) -result;
41120b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            }
412c109190b6984da6cba4cea44a0304b6da12d77e6Wu-cheng Li            return (float) result;
413a113a075ca9afa14361806ea592c8f078b1636c5Oscar Rydhe        } catch (NumberFormatException e) {
414a113a075ca9afa14361806ea592c8f078b1636c5Oscar Rydhe            // Some of the nubmers are not valid
415a113a075ca9afa14361806ea592c8f078b1636c5Oscar Rydhe            throw new IllegalArgumentException();
416a113a075ca9afa14361806ea592c8f078b1636c5Oscar Rydhe        } catch (ArrayIndexOutOfBoundsException e) {
417a113a075ca9afa14361806ea592c8f078b1636c5Oscar Rydhe            // Some of the rational does not follow the correct format
418a113a075ca9afa14361806ea592c8f078b1636c5Oscar Rydhe            throw new IllegalArgumentException();
41920b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync        }
42020b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    }
42120b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
42220b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    private native boolean appendThumbnailNative(String fileName,
42320b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            String thumbnailFileName);
42420b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
42520b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    private native void saveAttributesNative(String fileName,
42620b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync            String compressedAttributes);
42720b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
42820b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    private native String getAttributesNative(String fileName);
42920b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
43020b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    private native void commitChangesNative(String fileName);
43120b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync
43220b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync    private native byte[] getThumbnailNative(String fileName);
4336398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkey
4346398343e83b3fd11dd6536cf6f390a52c1e19d2eJeff Sharkey    private native long[] getThumbnailRangeNative(String fileName);
43520b03ea70bda3c4fb34e53cdf25cf98c4adb193frepo sync}
436