Merge "Extract the action bar interface." into gb-ub-photos-arches
diff --git a/src/com/android/gallery3d/exif/ExifData.java b/src/com/android/gallery3d/exif/ExifData.java
new file mode 100644
index 0000000..2c316f7
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ExifData.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.exif;
+
+
+public class ExifData {
+    public static final int TYPE_IFD_0 = 0;
+    public static final int TYPE_IFD_EXIF = 1;
+    public static final int TYPE_IFD_1 = 2;
+    public static final int TYPE_IFD_GPS = 3;
+    public static final int TYPE_IFD_INTEROPERABILITY = 4;
+
+    private final IfdData[] mIfdDatas = new IfdData[5];
+
+    public IfdData getIfdData(int ifdType) {
+        return mIfdDatas[ifdType];
+    }
+
+    public void addIfdData(IfdData data) {
+        mIfdDatas[data.getIfdType()] = data;
+    }
+}
diff --git a/src/com/android/gallery3d/exif/ExifReader.java b/src/com/android/gallery3d/exif/ExifReader.java
new file mode 100644
index 0000000..f406cb7
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ExifReader.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.exif;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ExifReader {
+
+    public ExifData getExifData(InputStream inputStream) throws ExifInvalidFormatException,
+            IOException {
+        ExifParser parser = new ExifParser();
+        IfdParser ifdParser = parser.parse(inputStream);
+        ExifData exifData = new ExifData();
+        IfdData ifdData = new IfdData(ExifData.TYPE_IFD_0);
+        parseIfd(ifdParser, ifdData, exifData);
+        exifData.addIfdData(ifdData);
+        return exifData;
+    }
+
+    public void parseIfd(IfdParser ifdParser, IfdData ifdData, ExifData exifData)
+            throws IOException, ExifInvalidFormatException {
+        int type = ifdParser.next();
+        while (type != IfdParser.TYPE_END) {
+            switch (type) {
+                case IfdParser.TYPE_NEW_TAG:
+                    ExifTag tag = ifdParser.readTag();
+                    if (tag.getDataSize() > 4) {
+                        long offset = ifdParser.readUnsignedInt();
+                        ifdParser.waitValueOfTag(tag, offset);
+                    } else if (tag.getTagId() == ExifTag.TIFF_TAG.TAG_EXIF_IFD
+                            || tag.getTagId() == ExifTag.TIFF_TAG.TAG_GPS_IFD
+                            || tag.getTagId() == ExifTag.EXIF_TAG.TAG_INTEROPERABILITY_IFD) {
+                        long offset = ifdParser.readUnsignedInt();
+                        ifdParser.waitValueOfTag(tag, offset);
+                        ifdData.addTag(tag, offset);
+                    } else {
+                        readAndSaveTag(tag, ifdParser, ifdData);
+                    }
+                    break;
+                case IfdParser.TYPE_NEXT_IFD:
+                    IfdData ifd1 = new IfdData(ExifData.TYPE_IFD_1);
+                    parseIfd(ifdParser.parseIfdBlock(), ifd1, exifData);
+                    exifData.addIfdData(ifd1);
+                    break;
+                case IfdParser.TYPE_VALUE_OF_PREV_TAG:
+                    tag = ifdParser.getCorrespodingExifTag();
+                    if(tag.getTagId() == ExifTag.TIFF_TAG.TAG_EXIF_IFD) {
+                        IfdData ifd = new IfdData(ExifData.TYPE_IFD_EXIF);
+                        parseIfd(ifdParser.parseIfdBlock(), ifd, exifData);
+                        exifData.addIfdData(ifd);
+                    } else if(tag.getTagId() == ExifTag.TIFF_TAG.TAG_GPS_IFD) {
+                        IfdData ifd = new IfdData(ExifData.TYPE_IFD_GPS);
+                        parseIfd(ifdParser.parseIfdBlock(), ifd, exifData);
+                        exifData.addIfdData(ifd);
+                    } else if(tag.getTagId() == ExifTag.EXIF_TAG.TAG_INTEROPERABILITY_IFD) {
+                        IfdData ifd = new IfdData(ExifData.TYPE_IFD_INTEROPERABILITY);
+                        parseIfd(ifdParser.parseIfdBlock(), ifd, exifData);
+                        exifData.addIfdData(ifd);
+                    } else {
+                        readAndSaveTag(tag, ifdParser, ifdData);
+                    }
+                    break;
+            }
+            type = ifdParser.next();
+        }
+    }
+
+    public void readAndSaveTag(ExifTag tag, IfdParser parser, IfdData ifdData)
+            throws IOException {
+        switch(tag.getDataType()) {
+            case ExifTag.TYPE_BYTE:
+            {
+                byte buf[] = new byte[tag.getComponentCount()];
+                parser.read(buf);
+                ifdData.addTag(tag, buf);
+                break;
+            }
+            case ExifTag.TYPE_ASCII:
+                ifdData.addTag(tag, parser.readString(tag.getComponentCount()));
+                break;
+            case ExifTag.TYPE_INT:
+            {
+                long value[] = new long[tag.getComponentCount()];
+                for (int i = 0, n = value.length; i < n; i++) {
+                    value[i] = parser.readUnsignedInt();
+                }
+                ifdData.addTag(tag, value);
+                break;
+            }
+            case ExifTag.TYPE_RATIONAL:
+            {
+                Rational value[] = new Rational[tag.getComponentCount()];
+                for (int i = 0, n = value.length; i < n; i++) {
+                    value[i] = parser.readUnsignedRational();
+                }
+                ifdData.addTag(tag, value);
+                break;
+            }
+            case ExifTag.TYPE_SHORT:
+            {
+                int value[] = new int[tag.getComponentCount()];
+                for (int i = 0, n = value.length; i < n; i++) {
+                    value[i] = parser.readUnsignedShort();
+                }
+                ifdData.addTag(tag, value);
+                break;
+            }
+            case ExifTag.TYPE_SINT:
+            {
+                int value[] = new int[tag.getComponentCount()];
+                for (int i = 0, n = value.length; i < n; i++) {
+                    value[i] = parser.readInt();
+                }
+                ifdData.addTag(tag, value);
+                break;
+            }
+            case ExifTag.TYPE_SRATIONAL:
+            {
+                Rational value[] = new Rational[tag.getComponentCount()];
+                for (int i = 0, n = value.length; i < n; i++) {
+                    value[i] = parser.readRational();
+                }
+                ifdData.addTag(tag, value);
+                break;
+            }
+            case ExifTag.TYPE_UNDEFINED:
+            {
+                byte buf[] = new byte[tag.getComponentCount()];
+                parser.read(buf);
+                ifdData.addTag(tag, buf);
+                break;
+            }
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/exif/IfdData.java b/src/com/android/gallery3d/exif/IfdData.java
new file mode 100644
index 0000000..b0d0630
--- /dev/null
+++ b/src/com/android/gallery3d/exif/IfdData.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.exif;
+
+import java.lang.reflect.Array;
+import java.util.HashMap;
+import java.util.Map;
+
+public class IfdData {
+
+    private final int mIfdType;
+    private final Map<Short, ExifTag> mExifTags = new HashMap<Short, ExifTag>();
+    private final Map<Short, Object> mValues = new HashMap<Short, Object>();
+
+    public IfdData(int ifdType) {
+        mIfdType = ifdType;
+    }
+
+    public ExifTag[] getAllTags(ExifTag[] outTag) {
+        return mExifTags.values().toArray(outTag);
+    }
+
+    public int getIfdType() {
+        return mIfdType;
+    }
+
+    public ExifTag getTag(short tagId) {
+        return mExifTags.get(tagId);
+    }
+
+    public short getShort(short tagId, int index) {
+        return (Short) Array.get(mValues.get(tagId), index);
+    }
+
+    public short getShort(short tagId) {
+        return (Short) Array.get(mValues.get(tagId), 0);
+    }
+
+    public int getUnsignedShort(short tagId, int index) {
+        return (Integer) Array.get(mValues.get(tagId), index);
+    }
+
+    public int getUnsignedShort(short tagId) {
+        return (Integer) Array.get(mValues.get(tagId), 0);
+    }
+
+    public int getInt(short tagId, int index) {
+        return (Integer) Array.get(mValues.get(tagId), index);
+    }
+
+    public int getInt(short tagId) {
+        return (Integer) Array.get(mValues.get(tagId), 0);
+    }
+
+    public long getUnsignedInt(short tagId, int index) {
+        return (Long) Array.get(mValues.get(tagId), index);
+    }
+
+    public long getUnsignedInt(short tagId) {
+        return (Long) Array.get(mValues.get(tagId), 0);
+    }
+
+    public String getString(short tagId) {
+        return (String) mValues.get(tagId);
+    }
+
+    public Rational getRational(short tagId, int index) {
+        return ((Rational[]) mValues.get(tagId))[index];
+    }
+
+    public Rational getRational(short tagId) {
+        return ((Rational[]) mValues.get(tagId))[0];
+    }
+
+    public int getBytes(short tagId, byte[] buf) {
+        return getBytes(tagId, buf, 0, buf.length);
+    }
+
+    public int getBytes(short tagId, byte[] buf, int offset, int length) {
+        Object data = mValues.get(tagId);
+        if (Array.getLength(data) < length + offset) {
+            System.arraycopy(data, offset, buf, 0, Array.getLength(data) - offset);
+            return Array.getLength(data) - offset;
+        } else {
+            System.arraycopy(data, offset, buf, 0, length);
+            return length;
+        }
+    }
+
+    public void addTag(ExifTag tag, Object object) {
+        mExifTags.put(tag.getTagId(),  tag);
+        if (object.getClass().isArray() || object.getClass() == String.class) {
+            mValues.put(tag.getTagId(), object);
+        } else {
+            Object array = Array.newInstance(object.getClass(), 1);
+            Array.set(array, 0, object);
+            mValues.put(tag.getTagId(), array);
+        }
+    }
+}
diff --git a/tests/res/xml/galaxy_nexus.xml b/tests/res/xml/galaxy_nexus.xml
index 62665a9..cefd078 100644
--- a/tests/res/xml/galaxy_nexus.xml
+++ b/tests/res/xml/galaxy_nexus.xml
@@ -8,6 +8,7 @@
         <tag id="0x131" name="Software">MASTER</tag>
         <tag id="0x132" name="DateTime">2012:07:30 16:28:42</tag>
         <tag id="0x213" name="YCbCrPositioning">1</tag>
+        <tag id="0x8769" name="ExifOffset">164</tag>
     </ifd>
     <ifd name="exif-ifd">
         <tag id="0x829A" name="ExposureTime">1/40</tag>
diff --git a/tests/src/com/android/gallery3d/exif/ExifParserTest.java b/tests/src/com/android/gallery3d/exif/ExifParserTest.java
index 8ae25e5..86ef12f 100644
--- a/tests/src/com/android/gallery3d/exif/ExifParserTest.java
+++ b/tests/src/com/android/gallery3d/exif/ExifParserTest.java
@@ -22,9 +22,6 @@
 import android.test.InstrumentationTestCase;
 import android.util.Log;
 
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.HashMap;
@@ -42,16 +39,6 @@
 
     private InputStream mImageInputStream;
 
-    private static final String XML_EXIF_TAG = "exif";
-    private static final String XML_IFD_TAG = "ifd";
-    private static final String XML_IFD_NAME = "name";
-    private static final String XML_TAG = "tag";
-    private static final String XML_IFD0 = "ifd0";
-    private static final String XML_IFD1 = "ifd1";
-    private static final String XML_EXIF_IFD = "exif-ifd";
-    private static final String XML_INTEROPERABILITY_IFD = "interoperability-ifd";
-    private static final String XML_TAG_ID = "id";
-
     public ExifParserTest(int imageResourceId, int xmlResourceId) {
         mImageResourceId = imageResourceId;
         mXmlResourceId = xmlResourceId;
@@ -65,63 +52,11 @@
         XmlResourceParser parser =
                 getInstrumentation().getContext().getResources().getXml(mXmlResourceId);
 
-        while (parser.next() != XmlPullParser.END_DOCUMENT) {
-            if (parser.getEventType() == XmlPullParser.START_TAG) {
-                assert(parser.getName().equals(XML_EXIF_TAG));
-                readXml(parser);
-                break;
-            }
-        }
+        ExifXmlReader.readXml(parser, mIfd0Value, mIfd1Value, mExifIfdValue
+                , mInteroperabilityIfdValue);
         parser.close();
     }
 
-    private void readXml(XmlPullParser parser) throws XmlPullParserException,
-            IOException {
-        parser.require(XmlPullParser.START_TAG, null, XML_EXIF_TAG);
-        while (parser.next() != XmlPullParser.END_TAG) {
-            if (parser.getEventType() == XmlPullParser.START_TAG) {
-                readXmlIfd(parser);
-            }
-        }
-        parser.require(XmlPullParser.END_TAG, null, XML_EXIF_TAG);
-    }
-
-    private void readXmlIfd(XmlPullParser parser) throws XmlPullParserException, IOException {
-        parser.require(XmlPullParser.START_TAG, null, XML_IFD_TAG);
-        String name = parser.getAttributeValue(null, XML_IFD_NAME);
-        HashMap<Short, String> ifdData = null;
-        if (XML_IFD0.equals(name)) {
-            ifdData = mIfd0Value;
-        } else if (XML_IFD1.equals(name)) {
-            ifdData = mIfd1Value;
-        } else if (XML_EXIF_IFD.equals(name)) {
-            ifdData = mExifIfdValue;
-        } else if (XML_INTEROPERABILITY_IFD.equals(name)) {
-            ifdData = mInteroperabilityIfdValue;
-        } else {
-            throw new RuntimeException("Unknown IFD name in xml file: " + name);
-        }
-        while (parser.next() != XmlPullParser.END_TAG) {
-            if (parser.getEventType() == XmlPullParser.START_TAG) {
-                readXmlTag(parser, ifdData);
-            }
-        }
-        parser.require(XmlPullParser.END_TAG, null, XML_IFD_TAG);
-    }
-
-    private void readXmlTag(XmlPullParser parser, HashMap<Short, String> data)
-            throws XmlPullParserException, IOException {
-        parser.require(XmlPullParser.START_TAG, null, XML_TAG);
-        short id = Integer.decode(parser.getAttributeValue(null, XML_TAG_ID)).shortValue();
-        String value = "";
-        if (parser.next() == XmlPullParser.TEXT) {
-            value = parser.getText();
-            parser.next();
-        }
-        data.put(id, value);
-        parser.require(XmlPullParser.END_TAG, null, XML_TAG);
-    }
-
     public void testParse() throws IOException, ExifInvalidFormatException {
         ExifParser parser = new ExifParser();
         parseIfd0(parser.parse(mImageInputStream));
@@ -143,8 +78,8 @@
                         ifdParser.waitValueOfTag(tag, offset);
                     } else {
                         checkTag(tag, ifdParser, mIfd0Value);
-                        tagNumber++;
                     }
+                    tagNumber++;
                     break;
                 case IfdParser.TYPE_NEXT_IFD:
                     parseIfd1(ifdParser.parseIfdBlock());
@@ -157,7 +92,6 @@
                         isEnterExifIfd = true;
                     } else {
                         checkTag(ifdParser.getCorrespodingExifTag(), ifdParser, mIfd0Value);
-                        tagNumber++;
                     }
                     break;
             }
@@ -182,15 +116,14 @@
                         ifdParser.waitValueOfTag(tag, offset);
                     } else {
                         checkTag(tag, ifdParser, mIfd1Value);
-                        tagNumber++;
                     }
+                    tagNumber++;
                     break;
                 case IfdParser.TYPE_NEXT_IFD:
                     fail("Find a ifd after ifd1");
                     break;
                 case IfdParser.TYPE_VALUE_OF_PREV_TAG:
                     checkTag(ifdParser.getCorrespodingExifTag(), ifdParser, mIfd1Value);
-                    tagNumber++;
                     break;
             }
             type = ifdParser.next();
@@ -219,8 +152,8 @@
                         isHasInterIfd = true;
                     } else {
                         checkTag(tag, ifdParser, mExifIfdValue);
-                        tagNumber++;
                     }
+                    tagNumber++;
                     break;
                 case IfdParser.TYPE_NEXT_IFD:
                     fail("Find a ifd after exif ifd");
@@ -232,7 +165,6 @@
                         isEnterInterIfd = true;
                     } else {
                         checkTag(ifdParser.getCorrespodingExifTag(), ifdParser, mExifIfdValue);
-                        tagNumber++;
                     }
                     break;
             }
@@ -257,8 +189,8 @@
                         ifdParser.waitValueOfTag(tag, offset);
                     } else {
                         checkTag(tag, ifdParser, mInteroperabilityIfdValue);
-                        tagNumber++;
                     }
+                    tagNumber++;
                     break;
                 case IfdParser.TYPE_NEXT_IFD:
                     fail("Find a ifd after exif ifd");
@@ -266,7 +198,6 @@
                 case IfdParser.TYPE_VALUE_OF_PREV_TAG:
                     checkTag(ifdParser.getCorrespodingExifTag(), ifdParser
                             , mInteroperabilityIfdValue);
-                    tagNumber++;
                     break;
             }
             type = ifdParser.next();
diff --git a/tests/src/com/android/gallery3d/exif/ExifReaderTest.java b/tests/src/com/android/gallery3d/exif/ExifReaderTest.java
new file mode 100644
index 0000000..9964997
--- /dev/null
+++ b/tests/src/com/android/gallery3d/exif/ExifReaderTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.exif;
+
+import android.content.res.XmlResourceParser;
+import android.test.InstrumentationTestCase;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+
+public class ExifReaderTest extends InstrumentationTestCase {
+    private static final String TAG = "ExifReaderTest";
+
+    private final int mImageResourceId;
+    private final int mXmlResourceId;
+
+    private final HashMap<Short, String> mIfd0Value = new HashMap<Short, String>();
+    private final HashMap<Short, String> mIfd1Value = new HashMap<Short, String>();
+    private final HashMap<Short, String> mExifIfdValue = new HashMap<Short, String>();
+    private final HashMap<Short, String> mInteroperabilityIfdValue = new HashMap<Short, String>();
+
+    private InputStream mImageInputStream;
+
+    public ExifReaderTest(int imageResourceId, int xmlResourceId) {
+        mImageResourceId = imageResourceId;
+        mXmlResourceId = xmlResourceId;
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        mImageInputStream = getInstrumentation()
+                .getContext().getResources().openRawResource(mImageResourceId);
+
+        XmlResourceParser parser =
+                getInstrumentation().getContext().getResources().getXml(mXmlResourceId);
+
+        ExifXmlReader.readXml(parser, mIfd0Value, mIfd1Value, mExifIfdValue
+                , mInteroperabilityIfdValue);
+        parser.close();
+    }
+
+    public void testRead() throws ExifInvalidFormatException, IOException {
+        ExifReader reader = new ExifReader();
+        ExifData exifData = reader.getExifData(mImageInputStream);
+        checkIfd(exifData, ExifData.TYPE_IFD_0, mIfd0Value);
+        checkIfd(exifData, ExifData.TYPE_IFD_1, mIfd1Value);
+        checkIfd(exifData, ExifData.TYPE_IFD_EXIF, mExifIfdValue);
+        checkIfd(exifData, ExifData.TYPE_IFD_INTEROPERABILITY, mInteroperabilityIfdValue);
+    }
+
+    private void checkIfd(ExifData exifData, int ifdType, HashMap<Short, String> ifdValue)
+            throws IOException {
+        IfdData ifd = exifData.getIfdData(ifdType);
+        if (ifd == null) {
+            assertEquals(0 ,ifdValue.size());
+            return;
+        }
+        ExifTag[] tags = ifd.getAllTags(new ExifTag[0]);
+        for (ExifTag tag : tags) {
+            assertEquals(ifdValue.get(tag.getTagId()), readValueToString(tag, ifd));
+        }
+        assertEquals(ifdValue.size(), tags.length);
+    }
+
+    private String readValueToString(ExifTag tag, IfdData ifdData) throws IOException {
+        StringBuilder sbuilder = new StringBuilder();
+        switch(tag.getDataType()) {
+            case ExifTag.TYPE_UNDEFINED:
+            case ExifTag.TYPE_BYTE:
+                byte buf[] = new byte[tag.getComponentCount()];
+                ifdData.getBytes(tag.getTagId(), buf);
+                for(int i = 0; i < tag.getComponentCount(); i++) {
+                    if(i != 0) sbuilder.append(" ");
+                    sbuilder.append(String.format("%02x", buf[i]));
+                }
+                break;
+            case ExifTag.TYPE_ASCII:
+                // trim the string for comparison between xml
+                sbuilder.append(ifdData.getString(tag.getTagId()).trim());
+                break;
+            case ExifTag.TYPE_INT:
+                for(int i = 0; i < tag.getComponentCount(); i++) {
+                    if(i != 0) sbuilder.append(" ");
+                    sbuilder.append(ifdData.getUnsignedInt(tag.getTagId(), i));
+                }
+                break;
+            case ExifTag.TYPE_SRATIONAL:
+            case ExifTag.TYPE_RATIONAL:
+                for(int i = 0; i < tag.getComponentCount(); i++) {
+                    Rational r = ifdData.getRational(tag.getTagId(), i);
+                    if(i != 0) sbuilder.append(" ");
+                    sbuilder.append(r.getNominator()).append("/").append(r.getDenominator());
+                }
+                break;
+            case ExifTag.TYPE_SHORT:
+                for(int i = 0; i < tag.getComponentCount(); i++) {
+                    if(i != 0) sbuilder.append(" ");
+                    sbuilder.append(ifdData.getUnsignedShort(tag.getTagId(), i));
+                }
+                break;
+            case ExifTag.TYPE_SINT:
+                for(int i = 0; i < tag.getComponentCount(); i++) {
+                    if(i != 0) sbuilder.append(" ");
+                    sbuilder.append(ifdData.getInt(tag.getTagId(), i));
+                }
+                break;
+        }
+        return sbuilder.toString();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mImageInputStream.close();
+    }
+}
diff --git a/tests/src/com/android/gallery3d/exif/ExifTestRunner.java b/tests/src/com/android/gallery3d/exif/ExifTestRunner.java
index 7f7a228..022597d 100644
--- a/tests/src/com/android/gallery3d/exif/ExifTestRunner.java
+++ b/tests/src/com/android/gallery3d/exif/ExifTestRunner.java
@@ -16,17 +16,22 @@
 
 package com.android.gallery3d.exif;
 
+import android.test.InstrumentationTestCase;
 import android.test.InstrumentationTestRunner;
 import android.test.InstrumentationTestSuite;
+import android.util.Log;
 
 import com.android.gallery3d.tests.R;
 
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
 
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 
 public class ExifTestRunner extends InstrumentationTestRunner {
+    private static final String TAG = "ExifTestRunner";
+
     private static final int[] IMG_RESOURCE = {
         R.raw.galaxy_nexus
     };
@@ -37,16 +42,36 @@
     @Override
     public TestSuite getAllTests() {
         TestSuite suite = new InstrumentationTestSuite(this);
-        for (Method method : ExifParserTest.class.getDeclaredMethods()) {
+        getAllTestFromTestCase(ExifParserTest.class, suite);
+        getAllTestFromTestCase(ExifReaderTest.class, suite);
+        return suite;
+    }
+
+    private void getAllTestFromTestCase(Class<? extends InstrumentationTestCase> testClass,
+            TestSuite suite) {
+        for (Method method : testClass.getDeclaredMethods()) {
             if (method.getName().startsWith("test") && method.getParameterTypes().length == 0) {
                 for (int i = 0; i < IMG_RESOURCE.length; i++) {
-                    TestCase test = new ExifParserTest(IMG_RESOURCE[i], EXIF_DATA_RESOURCE[i]);
-                    test.setName(method.getName());
-                    suite.addTest(test);
+                    TestCase test;
+                    try {
+                        test = testClass.getDeclaredConstructor(int.class, int.class).
+                                newInstance(IMG_RESOURCE[i], EXIF_DATA_RESOURCE[i]);
+                        test.setName(method.getName());
+                        suite.addTest(test);
+                    } catch (IllegalArgumentException e) {
+                        Log.e(TAG, "Failed to create test case", e);
+                    } catch (InstantiationException e) {
+                        Log.e(TAG, "Failed to create test case", e);
+                    } catch (IllegalAccessException e) {
+                        Log.e(TAG, "Failed to create test case", e);
+                    } catch (InvocationTargetException e) {
+                        Log.e(TAG, "Failed to create test case", e);
+                    } catch (NoSuchMethodException e) {
+                        Log.e(TAG, "Failed to create test case", e);
+                    }
                 }
             }
         }
-        return suite;
     }
 
     @Override
diff --git a/tests/src/com/android/gallery3d/exif/ExifXmlReader.java b/tests/src/com/android/gallery3d/exif/ExifXmlReader.java
new file mode 100644
index 0000000..ea748cc
--- /dev/null
+++ b/tests/src/com/android/gallery3d/exif/ExifXmlReader.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.exif;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+public class ExifXmlReader {
+
+    private static final String XML_EXIF_TAG = "exif";
+    private static final String XML_IFD_TAG = "ifd";
+    private static final String XML_IFD_NAME = "name";
+    private static final String XML_TAG = "tag";
+    private static final String XML_IFD0 = "ifd0";
+    private static final String XML_IFD1 = "ifd1";
+    private static final String XML_EXIF_IFD = "exif-ifd";
+    private static final String XML_INTEROPERABILITY_IFD = "interoperability-ifd";
+    private static final String XML_TAG_ID = "id";
+
+    public static void readXml(XmlPullParser parser, HashMap<Short, String> ifd0,
+            HashMap<Short, String> ifd1, HashMap<Short, String> exifIfd,
+            HashMap<Short, String> interoperabilityIfd) throws XmlPullParserException,
+            IOException {
+
+        while (parser.next() != XmlPullParser.END_DOCUMENT) {
+            if (parser.getEventType() == XmlPullParser.START_TAG) {
+                break;
+            }
+        }
+
+        assert(parser.getName().equals(XML_EXIF_TAG));
+
+        parser.require(XmlPullParser.START_TAG, null, XML_EXIF_TAG);
+        while (parser.next() != XmlPullParser.END_TAG) {
+            if (parser.getEventType() == XmlPullParser.START_TAG) {
+                readXmlIfd(parser, ifd0, ifd1, exifIfd, interoperabilityIfd);
+            }
+        }
+        parser.require(XmlPullParser.END_TAG, null, XML_EXIF_TAG);
+    }
+
+    private static void readXmlIfd(XmlPullParser parser, HashMap<Short, String> ifd0,
+            HashMap<Short, String> ifd1, HashMap<Short, String> exifIfd,
+            HashMap<Short, String> interoperabilityIfd) throws XmlPullParserException,
+            IOException {
+        parser.require(XmlPullParser.START_TAG, null, XML_IFD_TAG);
+        String name = parser.getAttributeValue(null, XML_IFD_NAME);
+        HashMap<Short, String> ifdData = null;
+        if (XML_IFD0.equals(name)) {
+            ifdData = ifd0;
+        } else if (XML_IFD1.equals(name)) {
+            ifdData = ifd1;
+        } else if (XML_EXIF_IFD.equals(name)) {
+            ifdData = exifIfd;
+        } else if (XML_INTEROPERABILITY_IFD.equals(name)) {
+            ifdData = interoperabilityIfd;
+        } else {
+            throw new RuntimeException("Unknown IFD name in xml file: " + name);
+        }
+        while (parser.next() != XmlPullParser.END_TAG) {
+            if (parser.getEventType() == XmlPullParser.START_TAG) {
+                readXmlTag(parser, ifdData);
+            }
+        }
+        parser.require(XmlPullParser.END_TAG, null, XML_IFD_TAG);
+    }
+
+    private static void readXmlTag(XmlPullParser parser, HashMap<Short, String> data)
+        throws XmlPullParserException, IOException {
+        parser.require(XmlPullParser.START_TAG, null, XML_TAG);
+        short id = Integer.decode(parser.getAttributeValue(null, XML_TAG_ID)).shortValue();
+        String value = "";
+        if (parser.next() == XmlPullParser.TEXT) {
+            value = parser.getText();
+            parser.next();
+        }
+        data.put(id, value);
+        parser.require(XmlPullParser.END_TAG, null, XML_TAG);
+    }
+}