summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rwxr-xr-xtests/src/com/android/gallery3d/CameraTestRunner.java46
-rw-r--r--tests/src/com/android/gallery3d/anim/AnimationTest.java80
-rw-r--r--tests/src/com/android/gallery3d/common/BlobCacheTest.java738
-rw-r--r--tests/src/com/android/gallery3d/common/UtilsTest.java202
-rw-r--r--tests/src/com/android/gallery3d/data/GalleryAppMock.java50
-rw-r--r--tests/src/com/android/gallery3d/data/GalleryAppStub.java47
-rw-r--r--tests/src/com/android/gallery3d/data/LocalDataTest.java461
-rw-r--r--tests/src/com/android/gallery3d/data/MediaSetTest.java63
-rw-r--r--tests/src/com/android/gallery3d/data/MockItem.java53
-rw-r--r--tests/src/com/android/gallery3d/data/MockSet.java88
-rw-r--r--tests/src/com/android/gallery3d/data/MockSource.java48
-rw-r--r--tests/src/com/android/gallery3d/data/PathTest.java82
-rw-r--r--tests/src/com/android/gallery3d/data/RealDataTest.java110
-rw-r--r--tests/src/com/android/gallery3d/exif/ExifDataTest.java154
-rw-r--r--tests/src/com/android/gallery3d/exif/ExifInterfaceTest.java533
-rw-r--r--tests/src/com/android/gallery3d/exif/ExifModifierTest.java184
-rw-r--r--tests/src/com/android/gallery3d/exif/ExifOutputStreamTest.java198
-rw-r--r--tests/src/com/android/gallery3d/exif/ExifParserTest.java243
-rw-r--r--tests/src/com/android/gallery3d/exif/ExifReaderTest.java165
-rw-r--r--tests/src/com/android/gallery3d/exif/ExifTagTest.java219
-rw-r--r--tests/src/com/android/gallery3d/exif/ExifTestRunner.java135
-rw-r--r--tests/src/com/android/gallery3d/exif/ExifXmlDataTestCase.java108
-rw-r--r--tests/src/com/android/gallery3d/exif/ExifXmlReader.java125
-rw-r--r--tests/src/com/android/gallery3d/exif/Util.java195
-rw-r--r--tests/src/com/android/gallery3d/functional/CameraTest.java81
-rw-r--r--tests/src/com/android/gallery3d/functional/ImageCaptureIntentTest.java148
-rw-r--r--tests/src/com/android/gallery3d/functional/VideoCaptureIntentTest.java258
-rw-r--r--tests/src/com/android/gallery3d/glrenderer/GLCanvasMock.java71
-rw-r--r--tests/src/com/android/gallery3d/glrenderer/GLCanvasTest.java386
-rw-r--r--tests/src/com/android/gallery3d/glrenderer/GLMock.java197
-rw-r--r--tests/src/com/android/gallery3d/glrenderer/GLStub.java1490
-rw-r--r--tests/src/com/android/gallery3d/glrenderer/TextureTest.java208
-rw-r--r--tests/src/com/android/gallery3d/jpegstream/JpegStreamReaderTest.java84
-rw-r--r--tests/src/com/android/gallery3d/jpegstream/JpegStreamTestCase.java71
-rw-r--r--tests/src/com/android/gallery3d/jpegstream/JpegStreamTestRunner.java78
-rw-r--r--tests/src/com/android/gallery3d/jpegstream/JpegStreamWriterTest.java138
-rw-r--r--tests/src/com/android/gallery3d/ui/GLCanvasStub.java160
-rw-r--r--tests/src/com/android/gallery3d/ui/GLRootMock.java50
-rw-r--r--tests/src/com/android/gallery3d/ui/GLRootStub.java41
-rw-r--r--tests/src/com/android/gallery3d/ui/GLViewMock.java87
-rw-r--r--tests/src/com/android/gallery3d/ui/GLViewTest.java398
-rw-r--r--tests/src/com/android/gallery3d/ui/PointerInfo.java222
-rw-r--r--tests/src/com/android/gallery3d/unittest/CameraUnitTest.java107
-rw-r--r--tests/src/com/android/gallery3d/util/IntArrayTest.java60
-rw-r--r--tests/src/com/android/gallery3d/util/ProfileTest.java179
-rw-r--r--tests/src/com/android/photos/data/DataTestRunner.java46
-rw-r--r--tests/src/com/android/photos/data/MediaCacheTest.java388
-rw-r--r--tests/src/com/android/photos/data/PhotoDatabaseTest.java229
-rw-r--r--tests/src/com/android/photos/data/PhotoDatabaseUtils.java135
-rw-r--r--tests/src/com/android/photos/data/PhotoProviderTest.java391
-rw-r--r--tests/src/com/android/photos/data/TestHelper.java53
51 files changed, 10083 insertions, 0 deletions
diff --git a/tests/src/com/android/gallery3d/CameraTestRunner.java b/tests/src/com/android/gallery3d/CameraTestRunner.java
new file mode 100755
index 0000000..5032336
--- /dev/null
+++ b/tests/src/com/android/gallery3d/CameraTestRunner.java
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+
+import com.android.gallery3d.functional.CameraTest;
+import com.android.gallery3d.functional.ImageCaptureIntentTest;
+import com.android.gallery3d.functional.VideoCaptureIntentTest;
+import com.android.gallery3d.unittest.CameraUnitTest;
+
+import junit.framework.TestSuite;
+
+
+public class CameraTestRunner extends InstrumentationTestRunner {
+
+ @Override
+ public TestSuite getAllTests() {
+ TestSuite suite = new InstrumentationTestSuite(this);
+ suite.addTestSuite(CameraTest.class);
+ suite.addTestSuite(ImageCaptureIntentTest.class);
+ suite.addTestSuite(VideoCaptureIntentTest.class);
+ suite.addTestSuite(CameraUnitTest.class);
+ return suite;
+ }
+
+ @Override
+ public ClassLoader getLoader() {
+ return CameraTestRunner.class.getClassLoader();
+ }
+}
diff --git a/tests/src/com/android/gallery3d/anim/AnimationTest.java b/tests/src/com/android/gallery3d/anim/AnimationTest.java
new file mode 100644
index 0000000..c7d5dae
--- /dev/null
+++ b/tests/src/com/android/gallery3d/anim/AnimationTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2010 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.anim;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+import android.view.animation.Interpolator;
+
+import junit.framework.TestCase;
+
+@SmallTest
+public class AnimationTest extends TestCase {
+ private static final String TAG = "AnimationTest";
+
+ public void testFloatAnimation() {
+ FloatAnimation a = new FloatAnimation(0f, 1f, 10); // value 0 to 1.0, duration 10
+ a.start(); // start animation
+ assertTrue(a.isActive()); // should be active now
+ a.calculate(0); // set start time = 0
+ assertTrue(a.get() == 0); // start value should be 0
+ a.calculate(1); // calculate value for time 1
+ assertFloatEq(a.get(), 0.1f);
+ a.calculate(5); // calculate value for time 5
+ assertTrue(a.get() == 0.5);//
+ a.calculate(9); // calculate value for time 9
+ assertFloatEq(a.get(), 0.9f);
+ a.calculate(10); // calculate value for time 10
+ assertTrue(!a.isActive()); // should be inactive now
+ assertTrue(a.get() == 1.0);//
+ a.start(); // restart
+ assertTrue(a.isActive()); // should be active now
+ a.calculate(5); // set start time = 5
+ assertTrue(a.get() == 0); // start value should be 0
+ a.calculate(5+9); // calculate for time 5+9
+ assertFloatEq(a.get(), 0.9f);
+ }
+
+ private static class MyInterpolator implements Interpolator {
+ public float getInterpolation(float input) {
+ return 4f * (input - 0.5f); // maps [0,1] to [-2,2]
+ }
+ }
+
+ public void testInterpolator() {
+ FloatAnimation a = new FloatAnimation(0f, 1f, 10); // value 0 to 1.0, duration 10
+ a.setInterpolator(new MyInterpolator());
+ a.start(); // start animation
+ a.calculate(0); // set start time = 0
+ assertTrue(a.get() == -2); // start value should be -2
+ a.calculate(1); // calculate value for time 1
+ assertFloatEq(a.get(), -1.6f);
+ a.calculate(5); // calculate value for time 5
+ assertTrue(a.get() == 0); //
+ a.calculate(9); // calculate value for time 9
+ assertFloatEq(a.get(), 1.6f);
+ a.calculate(10); // calculate value for time 10
+ assertTrue(a.get() == 2); //
+ }
+
+ public static void assertFloatEq(float expected, float actual) {
+ if (Math.abs(actual - expected) > 1e-6) {
+ Log.v(TAG, "expected: " + expected + ", actual: " + actual);
+ fail();
+ }
+ }
+}
diff --git a/tests/src/com/android/gallery3d/common/BlobCacheTest.java b/tests/src/com/android/gallery3d/common/BlobCacheTest.java
new file mode 100644
index 0000000..2a911c4
--- /dev/null
+++ b/tests/src/com/android/gallery3d/common/BlobCacheTest.java
@@ -0,0 +1,738 @@
+/*
+ * Copyright (C) 2010 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.common;
+
+import com.android.gallery3d.common.BlobCache;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.Random;
+
+public class BlobCacheTest extends AndroidTestCase {
+ private static final String TAG = "BlobCacheTest";
+
+ @SmallTest
+ public void testReadIntLong() {
+ byte[] buf = new byte[9];
+ assertEquals(0, BlobCache.readInt(buf, 0));
+ assertEquals(0, BlobCache.readLong(buf, 0));
+ buf[0] = 1;
+ assertEquals(1, BlobCache.readInt(buf, 0));
+ assertEquals(1, BlobCache.readLong(buf, 0));
+ buf[3] = 0x7f;
+ assertEquals(0x7f000001, BlobCache.readInt(buf, 0));
+ assertEquals(0x7f000001, BlobCache.readLong(buf, 0));
+ assertEquals(0x007f0000, BlobCache.readInt(buf, 1));
+ assertEquals(0x007f0000, BlobCache.readLong(buf, 1));
+ buf[3] = (byte) 0x80;
+ buf[7] = (byte) 0xA0;
+ buf[0] = 0;
+ assertEquals(0x80000000, BlobCache.readInt(buf, 0));
+ assertEquals(0xA000000080000000L, BlobCache.readLong(buf, 0));
+ for (int i = 0; i < 8; i++) {
+ buf[i] = (byte) (0x11 * (i+8));
+ }
+ assertEquals(0xbbaa9988, BlobCache.readInt(buf, 0));
+ assertEquals(0xffeeddccbbaa9988L, BlobCache.readLong(buf, 0));
+ buf[8] = 0x33;
+ assertEquals(0x33ffeeddccbbaa99L, BlobCache.readLong(buf, 1));
+ }
+
+ @SmallTest
+ public void testWriteIntLong() {
+ byte[] buf = new byte[8];
+ BlobCache.writeInt(buf, 0, 0x12345678);
+ assertEquals(0x78, buf[0]);
+ assertEquals(0x56, buf[1]);
+ assertEquals(0x34, buf[2]);
+ assertEquals(0x12, buf[3]);
+ assertEquals(0x00, buf[4]);
+ BlobCache.writeLong(buf, 0, 0xffeeddccbbaa9988L);
+ for (int i = 0; i < 8; i++) {
+ assertEquals((byte) (0x11 * (i+8)), buf[i]);
+ }
+ }
+
+ @MediumTest
+ public void testChecksum() throws IOException {
+ BlobCache bc = new BlobCache(TEST_FILE_NAME, MAX_ENTRIES, MAX_BYTES, true);
+ byte[] buf = new byte[0];
+ assertEquals(0x1, bc.checkSum(buf));
+ buf = new byte[1];
+ assertEquals(0x10001, bc.checkSum(buf));
+ buf[0] = 0x47;
+ assertEquals(0x480048, bc.checkSum(buf));
+ buf = new byte[3];
+ buf[0] = 0x10;
+ buf[1] = 0x30;
+ buf[2] = 0x01;
+ assertEquals(0x940042, bc.checkSum(buf));
+ assertEquals(0x310031, bc.checkSum(buf, 1, 1));
+ assertEquals(0x1, bc.checkSum(buf, 1, 0));
+ assertEquals(0x630032, bc.checkSum(buf, 1, 2));
+ buf = new byte[1024];
+ for (int i = 0; i < buf.length; i++) {
+ buf[i] = (byte)(i*i);
+ }
+ assertEquals(0x3574a610, bc.checkSum(buf));
+ bc.close();
+ }
+
+ private static final int HEADER_SIZE = 32;
+ private static final int DATA_HEADER_SIZE = 4;
+ private static final int BLOB_HEADER_SIZE = 20;
+
+ private static final String TEST_FILE_NAME = "/sdcard/btest";
+ private static final int MAX_ENTRIES = 100;
+ private static final int MAX_BYTES = 1000;
+ private static final int INDEX_SIZE = HEADER_SIZE + MAX_ENTRIES * 12 * 2;
+ private static final long KEY_0 = 0x1122334455667788L;
+ private static final long KEY_1 = 0x1122334455667789L;
+ private static final long KEY_2 = 0x112233445566778AL;
+ private static byte[] DATA_0 = new byte[10];
+ private static byte[] DATA_1 = new byte[10];
+
+ @MediumTest
+ public void testBasic() throws IOException {
+ String name = TEST_FILE_NAME;
+ BlobCache bc;
+ File idxFile = new File(name + ".idx");
+ File data0File = new File(name + ".0");
+ File data1File = new File(name + ".1");
+
+ // Create a brand new cache.
+ bc = new BlobCache(name, MAX_ENTRIES, MAX_BYTES, true);
+ bc.close();
+
+ // Make sure the initial state is correct.
+ assertTrue(idxFile.exists());
+ assertTrue(data0File.exists());
+ assertTrue(data1File.exists());
+ assertEquals(INDEX_SIZE, idxFile.length());
+ assertEquals(DATA_HEADER_SIZE, data0File.length());
+ assertEquals(DATA_HEADER_SIZE, data1File.length());
+ assertEquals(0, bc.getActiveCount());
+
+ // Re-open it.
+ bc = new BlobCache(name, MAX_ENTRIES, MAX_BYTES, false);
+ assertNull(bc.lookup(KEY_0));
+
+ // insert one blob
+ genData(DATA_0, 1);
+ bc.insert(KEY_0, DATA_0);
+ assertSameData(DATA_0, bc.lookup(KEY_0));
+ assertEquals(1, bc.getActiveCount());
+ bc.close();
+
+ // Make sure the file size is right.
+ assertEquals(INDEX_SIZE, idxFile.length());
+ assertEquals(DATA_HEADER_SIZE + BLOB_HEADER_SIZE + DATA_0.length,
+ data0File.length());
+ assertEquals(DATA_HEADER_SIZE, data1File.length());
+
+ // Re-open it and make sure we can get the old data
+ bc = new BlobCache(name, MAX_ENTRIES, MAX_BYTES, false);
+ assertSameData(DATA_0, bc.lookup(KEY_0));
+
+ // insert with the same key (but using a different blob)
+ genData(DATA_0, 2);
+ bc.insert(KEY_0, DATA_0);
+ assertSameData(DATA_0, bc.lookup(KEY_0));
+ assertEquals(1, bc.getActiveCount());
+ bc.close();
+
+ // Make sure the file size is right.
+ assertEquals(INDEX_SIZE, idxFile.length());
+ assertEquals(DATA_HEADER_SIZE + 2 * (BLOB_HEADER_SIZE + DATA_0.length),
+ data0File.length());
+ assertEquals(DATA_HEADER_SIZE, data1File.length());
+
+ // Re-open it and make sure we can get the old data
+ bc = new BlobCache(name, MAX_ENTRIES, MAX_BYTES, false);
+ assertSameData(DATA_0, bc.lookup(KEY_0));
+
+ // insert another key and make sure we can get both key.
+ assertNull(bc.lookup(KEY_1));
+ genData(DATA_1, 3);
+ bc.insert(KEY_1, DATA_1);
+ assertSameData(DATA_0, bc.lookup(KEY_0));
+ assertSameData(DATA_1, bc.lookup(KEY_1));
+ assertEquals(2, bc.getActiveCount());
+ bc.close();
+
+ // Make sure the file size is right.
+ assertEquals(INDEX_SIZE, idxFile.length());
+ assertEquals(DATA_HEADER_SIZE + 3 * (BLOB_HEADER_SIZE + DATA_0.length),
+ data0File.length());
+ assertEquals(DATA_HEADER_SIZE, data1File.length());
+
+ // Re-open it and make sure we can get the old data
+ bc = new BlobCache(name, 100, 1000, false);
+ assertSameData(DATA_0, bc.lookup(KEY_0));
+ assertSameData(DATA_1, bc.lookup(KEY_1));
+ assertEquals(2, bc.getActiveCount());
+ bc.close();
+ }
+
+ @MediumTest
+ public void testNegativeKey() throws IOException {
+ BlobCache bc = new BlobCache(TEST_FILE_NAME, MAX_ENTRIES, MAX_BYTES, true);
+
+ // insert one blob
+ genData(DATA_0, 1);
+ bc.insert(-123, DATA_0);
+ assertSameData(DATA_0, bc.lookup(-123));
+ bc.close();
+ }
+
+ @MediumTest
+ public void testEmptyBlob() throws IOException {
+ BlobCache bc = new BlobCache(TEST_FILE_NAME, MAX_ENTRIES, MAX_BYTES, true);
+
+ byte[] data = new byte[0];
+ bc.insert(123, data);
+ assertSameData(data, bc.lookup(123));
+ bc.close();
+ }
+
+ @MediumTest
+ public void testLookupRequest() throws IOException {
+ BlobCache bc = new BlobCache(TEST_FILE_NAME, MAX_ENTRIES, MAX_BYTES, true);
+
+ // insert one blob
+ genData(DATA_0, 1);
+ bc.insert(1, DATA_0);
+ assertSameData(DATA_0, bc.lookup(1));
+
+ // the same size buffer
+ byte[] buf = new byte[DATA_0.length];
+ BlobCache.LookupRequest req = new BlobCache.LookupRequest();
+ req.key = 1;
+ req.buffer = buf;
+ assertTrue(bc.lookup(req));
+ assertEquals(1, req.key);
+ assertSame(buf, req.buffer);
+ assertEquals(DATA_0.length, req.length);
+
+ // larger buffer
+ buf = new byte[DATA_0.length + 22];
+ req = new BlobCache.LookupRequest();
+ req.key = 1;
+ req.buffer = buf;
+ assertTrue(bc.lookup(req));
+ assertEquals(1, req.key);
+ assertSame(buf, req.buffer);
+ assertEquals(DATA_0.length, req.length);
+
+ // smaller buffer
+ buf = new byte[DATA_0.length - 1];
+ req = new BlobCache.LookupRequest();
+ req.key = 1;
+ req.buffer = buf;
+ assertTrue(bc.lookup(req));
+ assertEquals(1, req.key);
+ assertNotSame(buf, req.buffer);
+ assertEquals(DATA_0.length, req.length);
+ assertSameData(DATA_0, req.buffer, DATA_0.length);
+
+ // null buffer
+ req = new BlobCache.LookupRequest();
+ req.key = 1;
+ req.buffer = null;
+ assertTrue(bc.lookup(req));
+ assertEquals(1, req.key);
+ assertNotNull(req.buffer);
+ assertEquals(DATA_0.length, req.length);
+ assertSameData(DATA_0, req.buffer, DATA_0.length);
+
+ bc.close();
+ }
+
+ @MediumTest
+ public void testKeyCollision() throws IOException {
+ BlobCache bc = new BlobCache(TEST_FILE_NAME, MAX_ENTRIES, MAX_BYTES, true);
+
+ for (int i = 0; i < MAX_ENTRIES / 2; i++) {
+ genData(DATA_0, i);
+ long key = KEY_1 + i * MAX_ENTRIES;
+ bc.insert(key, DATA_0);
+ }
+
+ for (int i = 0; i < MAX_ENTRIES / 2; i++) {
+ genData(DATA_0, i);
+ long key = KEY_1 + i * MAX_ENTRIES;
+ assertSameData(DATA_0, bc.lookup(key));
+ }
+ bc.close();
+ }
+
+ @MediumTest
+ public void testRegionFlip() throws IOException {
+ String name = TEST_FILE_NAME;
+ BlobCache bc;
+ File idxFile = new File(name + ".idx");
+ File data0File = new File(name + ".0");
+ File data1File = new File(name + ".1");
+
+ // Create a brand new cache.
+ bc = new BlobCache(name, MAX_ENTRIES, MAX_BYTES, true);
+
+ // This is the number of blobs fits into a region.
+ int maxFit = (MAX_BYTES - DATA_HEADER_SIZE) /
+ (BLOB_HEADER_SIZE + DATA_0.length);
+
+ for (int k = 0; k < maxFit; k++) {
+ genData(DATA_0, k);
+ bc.insert(k, DATA_0);
+ }
+ assertEquals(maxFit, bc.getActiveCount());
+
+ // Make sure the file size is right.
+ assertEquals(INDEX_SIZE, idxFile.length());
+ assertEquals(DATA_HEADER_SIZE + maxFit * (BLOB_HEADER_SIZE + DATA_0.length),
+ data0File.length());
+ assertEquals(DATA_HEADER_SIZE, data1File.length());
+
+ // Now insert another one and let it flip.
+ genData(DATA_0, 777);
+ bc.insert(KEY_1, DATA_0);
+ assertEquals(1, bc.getActiveCount());
+
+ assertEquals(INDEX_SIZE, idxFile.length());
+ assertEquals(DATA_HEADER_SIZE + maxFit * (BLOB_HEADER_SIZE + DATA_0.length),
+ data0File.length());
+ assertEquals(DATA_HEADER_SIZE + 1 * (BLOB_HEADER_SIZE + DATA_0.length),
+ data1File.length());
+
+ // Make sure we can find the new data
+ assertSameData(DATA_0, bc.lookup(KEY_1));
+
+ // Now find an old blob
+ int old = maxFit / 2;
+ genData(DATA_0, old);
+ assertSameData(DATA_0, bc.lookup(old));
+ assertEquals(2, bc.getActiveCount());
+
+ // Observed data is copied.
+ assertEquals(INDEX_SIZE, idxFile.length());
+ assertEquals(DATA_HEADER_SIZE + maxFit * (BLOB_HEADER_SIZE + DATA_0.length),
+ data0File.length());
+ assertEquals(DATA_HEADER_SIZE + 2 * (BLOB_HEADER_SIZE + DATA_0.length),
+ data1File.length());
+
+ // Now copy everything over (except we should have no space for the last one)
+ assertTrue(old < maxFit - 1);
+ for (int k = 0; k < maxFit; k++) {
+ genData(DATA_0, k);
+ assertSameData(DATA_0, bc.lookup(k));
+ }
+ assertEquals(maxFit, bc.getActiveCount());
+
+ // Now both file should be full.
+ assertEquals(INDEX_SIZE, idxFile.length());
+ assertEquals(DATA_HEADER_SIZE + maxFit * (BLOB_HEADER_SIZE + DATA_0.length),
+ data0File.length());
+ assertEquals(DATA_HEADER_SIZE + maxFit * (BLOB_HEADER_SIZE + DATA_0.length),
+ data1File.length());
+
+ // Now insert one to make it flip.
+ genData(DATA_0, 888);
+ bc.insert(KEY_2, DATA_0);
+ assertEquals(1, bc.getActiveCount());
+
+ // Check the size after the second flip.
+ assertEquals(INDEX_SIZE, idxFile.length());
+ assertEquals(DATA_HEADER_SIZE + 1 * (BLOB_HEADER_SIZE + DATA_0.length),
+ data0File.length());
+ assertEquals(DATA_HEADER_SIZE + maxFit * (BLOB_HEADER_SIZE + DATA_0.length),
+ data1File.length());
+
+ // Now the last key should be gone.
+ assertNull(bc.lookup(maxFit - 1));
+
+ // But others should remain
+ for (int k = 0; k < maxFit - 1; k++) {
+ genData(DATA_0, k);
+ assertSameData(DATA_0, bc.lookup(k));
+ }
+
+ assertEquals(maxFit, bc.getActiveCount());
+ genData(DATA_0, 777);
+ assertSameData(DATA_0, bc.lookup(KEY_1));
+ genData(DATA_0, 888);
+ assertSameData(DATA_0, bc.lookup(KEY_2));
+ assertEquals(maxFit, bc.getActiveCount());
+
+ // Now two files should be full.
+ assertEquals(INDEX_SIZE, idxFile.length());
+ assertEquals(DATA_HEADER_SIZE + maxFit * (BLOB_HEADER_SIZE + DATA_0.length),
+ data0File.length());
+ assertEquals(DATA_HEADER_SIZE + maxFit * (BLOB_HEADER_SIZE + DATA_0.length),
+ data1File.length());
+
+ bc.close();
+ }
+
+ @MediumTest
+ public void testEntryLimit() throws IOException {
+ String name = TEST_FILE_NAME;
+ BlobCache bc;
+ File idxFile = new File(name + ".idx");
+ File data0File = new File(name + ".0");
+ File data1File = new File(name + ".1");
+ int maxEntries = 10;
+ int maxFit = maxEntries / 2;
+ int indexSize = HEADER_SIZE + maxEntries * 12 * 2;
+
+ // Create a brand new cache with a small entry limit.
+ bc = new BlobCache(name, maxEntries, MAX_BYTES, true);
+
+ // Fill to just before flipping
+ for (int i = 0; i < maxFit; i++) {
+ genData(DATA_0, i);
+ bc.insert(i, DATA_0);
+ }
+ assertEquals(maxFit, bc.getActiveCount());
+
+ // Check the file size.
+ assertEquals(indexSize, idxFile.length());
+ assertEquals(DATA_HEADER_SIZE + maxFit * (BLOB_HEADER_SIZE + DATA_0.length),
+ data0File.length());
+ assertEquals(DATA_HEADER_SIZE, data1File.length());
+
+ // Insert one and make it flip
+ genData(DATA_0, 777);
+ bc.insert(777, DATA_0);
+ assertEquals(1, bc.getActiveCount());
+
+ // Check the file size.
+ assertEquals(indexSize, idxFile.length());
+ assertEquals(DATA_HEADER_SIZE + maxFit * (BLOB_HEADER_SIZE + DATA_0.length),
+ data0File.length());
+ assertEquals(DATA_HEADER_SIZE + 1 * (BLOB_HEADER_SIZE + DATA_0.length),
+ data1File.length());
+ bc.close();
+ }
+
+ @LargeTest
+ public void testDataIntegrity() throws IOException {
+ String name = TEST_FILE_NAME;
+ File idxFile = new File(name + ".idx");
+ File data0File = new File(name + ".0");
+ File data1File = new File(name + ".1");
+ RandomAccessFile f;
+
+ Log.v(TAG, "It should be readable if the content is not changed.");
+ prepareNewCache();
+ f = new RandomAccessFile(data0File, "rw");
+ f.seek(1);
+ byte b = f.readByte();
+ f.seek(1);
+ f.write(b);
+ f.close();
+ assertReadable();
+
+ Log.v(TAG, "Change the data file magic field");
+ prepareNewCache();
+ f = new RandomAccessFile(data0File, "rw");
+ f.seek(1);
+ f.write(0xFF);
+ f.close();
+ assertUnreadable();
+
+ prepareNewCache();
+ f = new RandomAccessFile(data1File, "rw");
+ f.write(0xFF);
+ f.close();
+ assertUnreadable();
+
+ Log.v(TAG, "Change the blob key");
+ prepareNewCache();
+ f = new RandomAccessFile(data0File, "rw");
+ f.seek(4);
+ f.write(0x00);
+ f.close();
+ assertUnreadable();
+
+ Log.v(TAG, "Change the blob checksum");
+ prepareNewCache();
+ f = new RandomAccessFile(data0File, "rw");
+ f.seek(4 + 8);
+ f.write(0x00);
+ f.close();
+ assertUnreadable();
+
+ Log.v(TAG, "Change the blob offset");
+ prepareNewCache();
+ f = new RandomAccessFile(data0File, "rw");
+ f.seek(4 + 12);
+ f.write(0x20);
+ f.close();
+ assertUnreadable();
+
+ Log.v(TAG, "Change the blob length: some other value");
+ prepareNewCache();
+ f = new RandomAccessFile(data0File, "rw");
+ f.seek(4 + 16);
+ f.write(0x20);
+ f.close();
+ assertUnreadable();
+
+ Log.v(TAG, "Change the blob length: -1");
+ prepareNewCache();
+ f = new RandomAccessFile(data0File, "rw");
+ f.seek(4 + 16);
+ f.writeInt(0xFFFFFFFF);
+ f.close();
+ assertUnreadable();
+
+ Log.v(TAG, "Change the blob length: big value");
+ prepareNewCache();
+ f = new RandomAccessFile(data0File, "rw");
+ f.seek(4 + 16);
+ f.writeInt(0xFFFFFF00);
+ f.close();
+ assertUnreadable();
+
+ Log.v(TAG, "Change the blob content");
+ prepareNewCache();
+ f = new RandomAccessFile(data0File, "rw");
+ f.seek(4 + 20);
+ f.write(0x01);
+ f.close();
+ assertUnreadable();
+
+ Log.v(TAG, "Change the index magic");
+ prepareNewCache();
+ f = new RandomAccessFile(idxFile, "rw");
+ f.seek(1);
+ f.write(0x00);
+ f.close();
+ assertUnreadable();
+
+ Log.v(TAG, "Change the active region");
+ prepareNewCache();
+ f = new RandomAccessFile(idxFile, "rw");
+ f.seek(12);
+ f.write(0x01);
+ f.close();
+ assertUnreadable();
+
+ Log.v(TAG, "Change the reserved data");
+ prepareNewCache();
+ f = new RandomAccessFile(idxFile, "rw");
+ f.seek(24);
+ f.write(0x01);
+ f.close();
+ assertUnreadable();
+
+ Log.v(TAG, "Change the checksum");
+ prepareNewCache();
+ f = new RandomAccessFile(idxFile, "rw");
+ f.seek(29);
+ f.write(0x00);
+ f.close();
+ assertUnreadable();
+
+ Log.v(TAG, "Change the key");
+ prepareNewCache();
+ f = new RandomAccessFile(idxFile, "rw");
+ f.seek(32 + 12 * (KEY_1 % MAX_ENTRIES));
+ f.write(0x00);
+ f.close();
+ assertUnreadable();
+
+ Log.v(TAG, "Change the offset");
+ prepareNewCache();
+ f = new RandomAccessFile(idxFile, "rw");
+ f.seek(32 + 12 * (KEY_1 % MAX_ENTRIES) + 8);
+ f.write(0x05);
+ f.close();
+ assertUnreadable();
+
+ Log.v(TAG, "Change the offset");
+ prepareNewCache();
+ f = new RandomAccessFile(idxFile, "rw");
+ f.seek(32 + 12 * (KEY_1 % MAX_ENTRIES) + 8 + 3);
+ f.write(0xFF);
+ f.close();
+ assertUnreadable();
+
+ Log.v(TAG, "Garbage index");
+ prepareNewCache();
+ f = new RandomAccessFile(idxFile, "rw");
+ int n = (int) idxFile.length();
+ f.seek(32);
+ byte[] garbage = new byte[1024];
+ for (int i = 0; i < garbage.length; i++) {
+ garbage[i] = (byte) 0x80;
+ }
+ int i = 32;
+ while (i < n) {
+ int todo = Math.min(garbage.length, n - i);
+ f.write(garbage, 0, todo);
+ i += todo;
+ }
+ f.close();
+ assertUnreadable();
+ }
+
+ // Create a brand new cache and put one entry into it.
+ private void prepareNewCache() throws IOException {
+ BlobCache bc = new BlobCache(TEST_FILE_NAME, MAX_ENTRIES, MAX_BYTES, true);
+ genData(DATA_0, 777);
+ bc.insert(KEY_1, DATA_0);
+ bc.close();
+ }
+
+ private void assertReadable() throws IOException {
+ BlobCache bc = new BlobCache(TEST_FILE_NAME, MAX_ENTRIES, MAX_BYTES, false);
+ genData(DATA_0, 777);
+ assertSameData(DATA_0, bc.lookup(KEY_1));
+ bc.close();
+ }
+
+ private void assertUnreadable() throws IOException {
+ BlobCache bc = new BlobCache(TEST_FILE_NAME, MAX_ENTRIES, MAX_BYTES, false);
+ genData(DATA_0, 777);
+ assertNull(bc.lookup(KEY_1));
+ bc.close();
+ }
+
+ @LargeTest
+ public void testRandomSize() throws IOException {
+ BlobCache bc = new BlobCache(TEST_FILE_NAME, MAX_ENTRIES, MAX_BYTES, true);
+
+ // Random size test
+ Random rand = new Random(0);
+ for (int i = 0; i < 100; i++) {
+ byte[] data = new byte[rand.nextInt(MAX_BYTES*12/10)];
+ try {
+ bc.insert(rand.nextLong(), data);
+ if (data.length > MAX_BYTES - 4 - 20) fail();
+ } catch (RuntimeException ex) {
+ if (data.length <= MAX_BYTES - 4 - 20) fail();
+ }
+ }
+
+ bc.close();
+ }
+
+ @LargeTest
+ public void testBandwidth() throws IOException {
+ BlobCache bc = new BlobCache(TEST_FILE_NAME, 1000, 10000000, true);
+
+ // Write
+ int count = 0;
+ byte[] data = new byte[20000];
+ long t0 = System.nanoTime();
+ for (int i = 0; i < 1000; i++) {
+ bc.insert(i, data);
+ count += data.length;
+ }
+ bc.syncAll();
+ float delta = (System.nanoTime() - t0) * 1e-3f;
+ Log.v(TAG, "write bandwidth = " + (count / delta) + " M/s");
+
+ // Copy over
+ BlobCache.LookupRequest req = new BlobCache.LookupRequest();
+ count = 0;
+ t0 = System.nanoTime();
+ for (int i = 0; i < 1000; i++) {
+ req.key = i;
+ req.buffer = data;
+ if (bc.lookup(req)) {
+ count += req.length;
+ }
+ }
+ bc.syncAll();
+ delta = (System.nanoTime() - t0) * 1e-3f;
+ Log.v(TAG, "copy over bandwidth = " + (count / delta) + " M/s");
+
+ // Read
+ count = 0;
+ t0 = System.nanoTime();
+ for (int i = 0; i < 1000; i++) {
+ req.key = i;
+ req.buffer = data;
+ if (bc.lookup(req)) {
+ count += req.length;
+ }
+ }
+ bc.syncAll();
+ delta = (System.nanoTime() - t0) * 1e-3f;
+ Log.v(TAG, "read bandwidth = " + (count / delta) + " M/s");
+
+ bc.close();
+ }
+
+ @LargeTest
+ public void testSmallSize() throws IOException {
+ BlobCache bc = new BlobCache(TEST_FILE_NAME, MAX_ENTRIES, 40, true);
+
+ // Small size test
+ Random rand = new Random(0);
+ for (int i = 0; i < 100; i++) {
+ byte[] data = new byte[rand.nextInt(3)];
+ bc.insert(rand.nextLong(), data);
+ }
+
+ bc.close();
+ }
+
+ @LargeTest
+ public void testManyEntries() throws IOException {
+ BlobCache bc = new BlobCache(TEST_FILE_NAME, 1, MAX_BYTES, true);
+
+ // Many entries test
+ Random rand = new Random(0);
+ for (int i = 0; i < 100; i++) {
+ byte[] data = new byte[rand.nextInt(10)];
+ }
+
+ bc.close();
+ }
+
+ private void genData(byte[] data, int seed) {
+ for(int i = 0; i < data.length; i++) {
+ data[i] = (byte) (seed * i);
+ }
+ }
+
+ private void assertSameData(byte[] data1, byte[] data2) {
+ if (data1 == null && data2 == null) return;
+ if (data1 == null || data2 == null) fail();
+ if (data1.length != data2.length) fail();
+ for (int i = 0; i < data1.length; i++) {
+ if (data1[i] != data2[i]) fail();
+ }
+ }
+
+ private void assertSameData(byte[] data1, byte[] data2, int n) {
+ if (data1 == null || data2 == null) fail();
+ for (int i = 0; i < n; i++) {
+ if (data1[i] != data2[i]) fail();
+ }
+ }
+}
diff --git a/tests/src/com/android/gallery3d/common/UtilsTest.java b/tests/src/com/android/gallery3d/common/UtilsTest.java
new file mode 100644
index 0000000..a20ebeb
--- /dev/null
+++ b/tests/src/com/android/gallery3d/common/UtilsTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2010 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.common;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+public class UtilsTest extends AndroidTestCase {
+ private static final String TAG = "UtilsTest";
+
+ private static final int [] testData = new int [] {
+ /* outWidth, outHeight, minSideLength, maxNumOfPixels, sample size */
+ 1, 1, BitmapUtils.UNCONSTRAINED, BitmapUtils.UNCONSTRAINED, 1,
+ 1, 1, 1, 1, 1,
+ 100, 100, 100, 10000, 1,
+ 100, 100, 100, 2500, 2,
+ 99, 66, 33, 10000, 2,
+ 66, 99, 33, 10000, 2,
+ 99, 66, 34, 10000, 1,
+ 99, 66, 22, 10000, 4,
+ 99, 66, 16, 10000, 4,
+
+ 10000, 10000, 20000, 1000000, 16,
+
+ 100, 100, 100, 10000, 1, // 1
+ 100, 100, 50, 10000, 2, // 2
+ 100, 100, 30, 10000, 4, // 3->4
+ 100, 100, 22, 10000, 4, // 4
+ 100, 100, 20, 10000, 8, // 5->8
+ 100, 100, 11, 10000, 16, // 9->16
+ 100, 100, 5, 10000, 24, // 20->24
+ 100, 100, 2, 10000, 56, // 50->56
+
+ 100, 100, 100, 10000 - 1, 2, // a bit less than 1
+ 100, 100, 100, 10000 / (2 * 2) - 1, 4, // a bit less than 2
+ 100, 100, 100, 10000 / (3 * 3) - 1, 4, // a bit less than 3
+ 100, 100, 100, 10000 / (4 * 4) - 1, 8, // a bit less than 4
+ 100, 100, 100, 10000 / (8 * 8) - 1, 16, // a bit less than 8
+ 100, 100, 100, 10000 / (16 * 16) - 1, 24, // a bit less than 16
+ 100, 100, 100, 10000 / (24 * 24) - 1, 32, // a bit less than 24
+ 100, 100, 100, 10000 / (32 * 32) - 1, 40, // a bit less than 32
+
+ 640, 480, 480, BitmapUtils.UNCONSTRAINED, 1, // 1
+ 640, 480, 240, BitmapUtils.UNCONSTRAINED, 2, // 2
+ 640, 480, 160, BitmapUtils.UNCONSTRAINED, 4, // 3->4
+ 640, 480, 120, BitmapUtils.UNCONSTRAINED, 4, // 4
+ 640, 480, 96, BitmapUtils.UNCONSTRAINED, 8, // 5->8
+ 640, 480, 80, BitmapUtils.UNCONSTRAINED, 8, // 6->8
+ 640, 480, 60, BitmapUtils.UNCONSTRAINED, 8, // 8
+ 640, 480, 48, BitmapUtils.UNCONSTRAINED, 16, // 10->16
+ 640, 480, 40, BitmapUtils.UNCONSTRAINED, 16, // 12->16
+ 640, 480, 30, BitmapUtils.UNCONSTRAINED, 16, // 16
+ 640, 480, 24, BitmapUtils.UNCONSTRAINED, 24, // 20->24
+ 640, 480, 20, BitmapUtils.UNCONSTRAINED, 24, // 24
+ 640, 480, 16, BitmapUtils.UNCONSTRAINED, 32, // 30->32
+ 640, 480, 12, BitmapUtils.UNCONSTRAINED, 40, // 40
+ 640, 480, 10, BitmapUtils.UNCONSTRAINED, 48, // 48
+ 640, 480, 8, BitmapUtils.UNCONSTRAINED, 64, // 60->64
+ 640, 480, 6, BitmapUtils.UNCONSTRAINED, 80, // 80
+ 640, 480, 4, BitmapUtils.UNCONSTRAINED, 120, // 120
+ 640, 480, 3, BitmapUtils.UNCONSTRAINED, 160, // 160
+ 640, 480, 2, BitmapUtils.UNCONSTRAINED, 240, // 240
+ 640, 480, 1, BitmapUtils.UNCONSTRAINED, 480, // 480
+
+ 640, 480, BitmapUtils.UNCONSTRAINED, BitmapUtils.UNCONSTRAINED, 1,
+ 640, 480, BitmapUtils.UNCONSTRAINED, 640 * 480, 1, // 1
+ 640, 480, BitmapUtils.UNCONSTRAINED, 640 * 480 - 1, 2, // a bit less than 1
+ 640, 480, BitmapUtils.UNCONSTRAINED, 640 * 480 / 4, 2, // 2
+ 640, 480, BitmapUtils.UNCONSTRAINED, 640 * 480 / 4 - 1, 4, // a bit less than 2
+ 640, 480, BitmapUtils.UNCONSTRAINED, 640 * 480 / 9, 4, // 3
+ 640, 480, BitmapUtils.UNCONSTRAINED, 640 * 480 / 9 - 1, 4, // a bit less than 3
+ 640, 480, BitmapUtils.UNCONSTRAINED, 640 * 480 / 16, 4, // 4
+ 640, 480, BitmapUtils.UNCONSTRAINED, 640 * 480 / 16 - 1, 8, // a bit less than 4
+ 640, 480, BitmapUtils.UNCONSTRAINED, 640 * 480 / 64, 8, // 8
+ 640, 480, BitmapUtils.UNCONSTRAINED, 640 * 480 / 64 - 1, 16, // a bit less than 8
+ 640, 480, BitmapUtils.UNCONSTRAINED, 640 * 480 / 256, 16, // 16
+ 640, 480, BitmapUtils.UNCONSTRAINED, 640 * 480 / 256 - 1, 24, // a bit less than 16
+ 640, 480, BitmapUtils.UNCONSTRAINED, 640 * 480 / (24 * 24) - 1, 32, // a bit less than 24
+ };
+
+ @SmallTest
+ public void testComputeSampleSize() {
+
+ for (int i = 0; i < testData.length; i += 5) {
+ int w = testData[i];
+ int h = testData[i + 1];
+ int minSide = testData[i + 2];
+ int maxPixels = testData[i + 3];
+ int sampleSize = testData[i + 4];
+ int result = BitmapUtils.computeSampleSize(w, h, minSide, maxPixels);
+ if (result != sampleSize) {
+ Log.v(TAG, w + "x" + h + ", minSide = " + minSide + ", maxPixels = "
+ + maxPixels + ", sampleSize = " + sampleSize + ", result = "
+ + result);
+ }
+ assertTrue(sampleSize == result);
+ }
+ }
+
+ public void testAssert() {
+ // This should not throw an exception.
+ Utils.assertTrue(true);
+
+ // This should throw an exception.
+ try {
+ Utils.assertTrue(false);
+ fail();
+ } catch (AssertionError ex) {
+ // expected.
+ }
+ }
+
+ public void testCheckNotNull() {
+ // These should not throw an expection.
+ Utils.checkNotNull(new Object());
+ Utils.checkNotNull(0);
+ Utils.checkNotNull("");
+
+ // This should throw an expection.
+ try {
+ Utils.checkNotNull(null);
+ fail();
+ } catch (NullPointerException ex) {
+ // expected.
+ }
+ }
+
+ public void testEquals() {
+ Object a = new Object();
+ Object b = new Object();
+
+ assertTrue(Utils.equals(null, null));
+ assertTrue(Utils.equals(a, a));
+ assertFalse(Utils.equals(null, a));
+ assertFalse(Utils.equals(a, null));
+ assertFalse(Utils.equals(a, b));
+ }
+
+ public void testNextPowerOf2() {
+ int[] q = new int[] {1, 2, 3, 4, 5, 6, 10, 65535, (1 << 30) - 1, (1 << 30)};
+ int[] a = new int[] {1, 2, 4, 4, 8, 8, 16, 65536, (1 << 30) , (1 << 30)};
+
+ for (int i = 0; i < q.length; i++) {
+ assertEquals(a[i], Utils.nextPowerOf2(q[i]));
+ }
+
+ int[] e = new int[] {0, -1, -2, -4, -65536, (1 << 30) + 1, Integer.MAX_VALUE};
+
+ for (int v : e) {
+ try {
+ Utils.nextPowerOf2(v);
+ fail();
+ } catch (IllegalArgumentException ex) {
+ // expected.
+ }
+ }
+ }
+
+ public void testClamp() {
+ assertEquals(1000, Utils.clamp(300, 1000, 2000));
+ assertEquals(1300, Utils.clamp(1300, 1000, 2000));
+ assertEquals(2000, Utils.clamp(2300, 1000, 2000));
+
+ assertEquals(0.125f, Utils.clamp(0.1f, 0.125f, 0.5f));
+ assertEquals(0.25f, Utils.clamp(0.25f, 0.125f, 0.5f));
+ assertEquals(0.5f, Utils.clamp(0.9f, 0.125f, 0.5f));
+ }
+
+ public void testIsOpaque() {
+ assertTrue(Utils.isOpaque(0xFF000000));
+ assertTrue(Utils.isOpaque(0xFFFFFFFF));
+ assertTrue(Utils.isOpaque(0xFF123456));
+
+ assertFalse(Utils.isOpaque(0xFEFFFFFF));
+ assertFalse(Utils.isOpaque(0x8FFFFFFF));
+ assertFalse(Utils.isOpaque(0x00FF0000));
+ assertFalse(Utils.isOpaque(0x5500FF00));
+ assertFalse(Utils.isOpaque(0xAA0000FF));
+ }
+
+ public static void assertFloatEq(float expected, float actual) {
+ if (Math.abs(actual - expected) > 1e-6) {
+ Log.v(TAG, "expected: " + expected + ", actual: " + actual);
+ fail();
+ }
+ }
+}
diff --git a/tests/src/com/android/gallery3d/data/GalleryAppMock.java b/tests/src/com/android/gallery3d/data/GalleryAppMock.java
new file mode 100644
index 0000000..bbc5692
--- /dev/null
+++ b/tests/src/com/android/gallery3d/data/GalleryAppMock.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2010 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.data;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.Looper;
+
+import com.android.gallery3d.ui.GLRoot;
+import com.android.gallery3d.ui.GLRootStub;
+
+class GalleryAppMock extends GalleryAppStub {
+ GLRoot mGLRoot = new GLRootStub();
+ DataManager mDataManager = new DataManager(this);
+ ContentResolver mResolver;
+ Context mContext;
+ Looper mMainLooper;
+
+ GalleryAppMock(Context context,
+ ContentResolver resolver, Looper mainLooper) {
+ mContext = context;
+ mResolver = resolver;
+ mMainLooper = mainLooper;
+ }
+
+ @Override
+ public GLRoot getGLRoot() { return mGLRoot; }
+ @Override
+ public DataManager getDataManager() { return mDataManager; }
+ @Override
+ public Context getAndroidContext() { return mContext; }
+ @Override
+ public ContentResolver getContentResolver() { return mResolver; }
+ @Override
+ public Looper getMainLooper() { return mMainLooper; }
+}
diff --git a/tests/src/com/android/gallery3d/data/GalleryAppStub.java b/tests/src/com/android/gallery3d/data/GalleryAppStub.java
new file mode 100644
index 0000000..5aff2a2
--- /dev/null
+++ b/tests/src/com/android/gallery3d/data/GalleryAppStub.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2010 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.data;
+
+import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.app.StateManager;
+import com.android.gallery3d.app.StitchingProgressManager;
+import com.android.gallery3d.ui.GLRoot;
+import com.android.gallery3d.util.ThreadPool;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Looper;
+
+class GalleryAppStub implements GalleryApp {
+ public ImageCacheService getImageCacheService() { return null; }
+ public StateManager getStateManager() { return null; }
+ public DataManager getDataManager() { return null; }
+ public DownloadUtils getDownloadService() { return null; }
+ public DecodeUtils getDecodeService() { return null; }
+
+ public GLRoot getGLRoot() { return null; }
+
+ public Context getAndroidContext() { return null; }
+
+ public Looper getMainLooper() { return null; }
+ public Resources getResources() { return null; }
+ public ContentResolver getContentResolver() { return null; }
+ public ThreadPool getThreadPool() { return null; }
+ public DownloadCache getDownloadCache() { return null; }
+ public StitchingProgressManager getStitchingProgressManager() { return null; }
+}
diff --git a/tests/src/com/android/gallery3d/data/LocalDataTest.java b/tests/src/com/android/gallery3d/data/LocalDataTest.java
new file mode 100644
index 0000000..8f6a46b
--- /dev/null
+++ b/tests/src/com/android/gallery3d/data/LocalDataTest.java
@@ -0,0 +1,461 @@
+/*
+ * Copyright (C) 2010 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.data;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.os.Looper;
+import android.test.AndroidTestCase;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class LocalDataTest extends AndroidTestCase {
+ @SuppressWarnings("unused")
+ private static final String TAG = "LocalDataTest";
+ private static final long DEFAULT_TIMEOUT = 1000; // one second
+
+ @MediumTest
+ public void testLocalAlbum() throws Exception {
+ new TestZeroImage().run();
+ new TestOneImage().run();
+ new TestMoreImages().run();
+ new TestZeroVideo().run();
+ new TestOneVideo().run();
+ new TestMoreVideos().run();
+ new TestDeleteOneImage().run();
+ new TestDeleteOneAlbum().run();
+ }
+
+ abstract class TestLocalAlbumBase {
+ private boolean mIsImage;
+ protected GalleryAppStub mApp;
+ protected LocalAlbumSet mAlbumSet;
+
+ TestLocalAlbumBase(boolean isImage) {
+ mIsImage = isImage;
+ }
+
+ public void run() throws Exception {
+ SQLiteDatabase db = SQLiteDatabase.create(null);
+ prepareData(db);
+ mApp = newGalleryContext(db, Looper.getMainLooper());
+ Path.clearAll();
+ Path path = Path.fromString(
+ mIsImage ? "/local/image" : "/local/video");
+ mAlbumSet = new LocalAlbumSet(path, mApp);
+ mAlbumSet.reload();
+ verifyResult();
+ }
+
+ abstract void prepareData(SQLiteDatabase db);
+ abstract void verifyResult() throws Exception;
+ }
+
+ abstract class TestLocalImageAlbum extends TestLocalAlbumBase {
+ TestLocalImageAlbum() {
+ super(true);
+ }
+ }
+
+ abstract class TestLocalVideoAlbum extends TestLocalAlbumBase {
+ TestLocalVideoAlbum() {
+ super(false);
+ }
+ }
+
+ class TestZeroImage extends TestLocalImageAlbum {
+ @Override
+ public void prepareData(SQLiteDatabase db) {
+ createImageTable(db);
+ }
+
+ @Override
+ public void verifyResult() {
+ assertEquals(0, mAlbumSet.getMediaItemCount());
+ assertEquals(0, mAlbumSet.getSubMediaSetCount());
+ assertEquals(0, mAlbumSet.getTotalMediaItemCount());
+ }
+ }
+
+ class TestOneImage extends TestLocalImageAlbum {
+ @Override
+ public void prepareData(SQLiteDatabase db) {
+ createImageTable(db);
+ insertImageData(db);
+ }
+
+ @Override
+ public void verifyResult() {
+ assertEquals(0, mAlbumSet.getMediaItemCount());
+ assertEquals(1, mAlbumSet.getSubMediaSetCount());
+ assertEquals(1, mAlbumSet.getTotalMediaItemCount());
+ MediaSet sub = mAlbumSet.getSubMediaSet(0);
+ assertEquals(1, sub.getMediaItemCount());
+ assertEquals(0, sub.getSubMediaSetCount());
+ LocalMediaItem item = (LocalMediaItem) sub.getMediaItem(0, 1).get(0);
+ assertEquals(1, item.id);
+ assertEquals("IMG_0072", item.caption);
+ assertEquals("image/jpeg", item.mimeType);
+ assertEquals(12.0, item.latitude);
+ assertEquals(34.0, item.longitude);
+ assertEquals(0xD000, item.dateTakenInMs);
+ assertEquals(1280395646L, item.dateAddedInSec);
+ assertEquals(1275934796L, item.dateModifiedInSec);
+ assertEquals("/mnt/sdcard/DCIM/100CANON/IMG_0072.JPG", item.filePath);
+ }
+ }
+
+ class TestMoreImages extends TestLocalImageAlbum {
+ @Override
+ public void prepareData(SQLiteDatabase db) {
+ // Albums are sorted by names, and items are sorted by
+ // dateTimeTaken (descending)
+ createImageTable(db);
+ // bucket 0xB000
+ insertImageData(db, 1000, 0xB000, "second"); // id 1
+ insertImageData(db, 2000, 0xB000, "second"); // id 2
+ // bucket 0xB001
+ insertImageData(db, 3000, 0xB001, "first"); // id 3
+ }
+
+ @Override
+ public void verifyResult() {
+ assertEquals(0, mAlbumSet.getMediaItemCount());
+ assertEquals(2, mAlbumSet.getSubMediaSetCount());
+ assertEquals(3, mAlbumSet.getTotalMediaItemCount());
+
+ MediaSet first = mAlbumSet.getSubMediaSet(0);
+ assertEquals(1, first.getMediaItemCount());
+ LocalMediaItem item = (LocalMediaItem) first.getMediaItem(0, 1).get(0);
+ assertEquals(3, item.id);
+ assertEquals(3000L, item.dateTakenInMs);
+
+ MediaSet second = mAlbumSet.getSubMediaSet(1);
+ assertEquals(2, second.getMediaItemCount());
+ item = (LocalMediaItem) second.getMediaItem(0, 1).get(0);
+ assertEquals(2, item.id);
+ assertEquals(2000L, item.dateTakenInMs);
+ item = (LocalMediaItem) second.getMediaItem(1, 1).get(0);
+ assertEquals(1, item.id);
+ assertEquals(1000L, item.dateTakenInMs);
+ }
+ }
+
+ class OnContentDirtyLatch implements ContentListener {
+ private CountDownLatch mLatch = new CountDownLatch(1);
+
+ public void onContentDirty() {
+ mLatch.countDown();
+ }
+
+ public boolean isOnContentDirtyBeCalled(long timeout)
+ throws InterruptedException {
+ return mLatch.await(timeout, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ class TestDeleteOneAlbum extends TestLocalImageAlbum {
+ @Override
+ public void prepareData(SQLiteDatabase db) {
+ // Albums are sorted by names, and items are sorted by
+ // dateTimeTaken (descending)
+ createImageTable(db);
+ // bucket 0xB000
+ insertImageData(db, 1000, 0xB000, "second"); // id 1
+ insertImageData(db, 2000, 0xB000, "second"); // id 2
+ // bucket 0xB001
+ insertImageData(db, 3000, 0xB001, "first"); // id 3
+ }
+
+ @Override
+ public void verifyResult() throws Exception {
+ MediaSet sub = mAlbumSet.getSubMediaSet(1); // "second"
+ assertEquals(2, mAlbumSet.getSubMediaSetCount());
+ OnContentDirtyLatch latch = new OnContentDirtyLatch();
+ sub.addContentListener(latch);
+ assertTrue((sub.getSupportedOperations() & MediaSet.SUPPORT_DELETE) != 0);
+ sub.delete();
+ mAlbumSet.fakeChange();
+ latch.isOnContentDirtyBeCalled(DEFAULT_TIMEOUT);
+ mAlbumSet.reload();
+ assertEquals(1, mAlbumSet.getSubMediaSetCount());
+ }
+ }
+
+ class TestDeleteOneImage extends TestLocalImageAlbum {
+
+ @Override
+ public void prepareData(SQLiteDatabase db) {
+ createImageTable(db);
+ insertImageData(db);
+ }
+
+ @Override
+ public void verifyResult() {
+ MediaSet sub = mAlbumSet.getSubMediaSet(0);
+ LocalMediaItem item = (LocalMediaItem) sub.getMediaItem(0, 1).get(0);
+ assertEquals(1, sub.getMediaItemCount());
+ assertTrue((sub.getSupportedOperations() & MediaSet.SUPPORT_DELETE) != 0);
+ sub.delete();
+ sub.reload();
+ assertEquals(0, sub.getMediaItemCount());
+ }
+ }
+
+ static void createImageTable(SQLiteDatabase db) {
+ // This is copied from MediaProvider
+ db.execSQL("CREATE TABLE IF NOT EXISTS images (" +
+ "_id INTEGER PRIMARY KEY," +
+ "_data TEXT," +
+ "_size INTEGER," +
+ "_display_name TEXT," +
+ "mime_type TEXT," +
+ "title TEXT," +
+ "date_added INTEGER," +
+ "date_modified INTEGER," +
+ "description TEXT," +
+ "picasa_id TEXT," +
+ "isprivate INTEGER," +
+ "latitude DOUBLE," +
+ "longitude DOUBLE," +
+ "datetaken INTEGER," +
+ "orientation INTEGER," +
+ "mini_thumb_magic INTEGER," +
+ "bucket_id TEXT," +
+ "bucket_display_name TEXT" +
+ ");");
+ }
+
+ static void insertImageData(SQLiteDatabase db) {
+ insertImageData(db, 0xD000, 0xB000, "name");
+ }
+
+ static void insertImageData(SQLiteDatabase db, long dateTaken,
+ int bucketId, String bucketName) {
+ db.execSQL("INSERT INTO images (title, mime_type, latitude, longitude, "
+ + "datetaken, date_added, date_modified, bucket_id, "
+ + "bucket_display_name, _data, orientation) "
+ + "VALUES ('IMG_0072', 'image/jpeg', 12, 34, "
+ + dateTaken + ", 1280395646, 1275934796, '" + bucketId + "', "
+ + "'" + bucketName + "', "
+ + "'/mnt/sdcard/DCIM/100CANON/IMG_0072.JPG', 0)");
+ }
+
+ class TestZeroVideo extends TestLocalVideoAlbum {
+ @Override
+ public void prepareData(SQLiteDatabase db) {
+ createVideoTable(db);
+ }
+
+ @Override
+ public void verifyResult() {
+ assertEquals(0, mAlbumSet.getMediaItemCount());
+ assertEquals(0, mAlbumSet.getSubMediaSetCount());
+ assertEquals(0, mAlbumSet.getTotalMediaItemCount());
+ }
+ }
+
+ class TestOneVideo extends TestLocalVideoAlbum {
+ @Override
+ public void prepareData(SQLiteDatabase db) {
+ createVideoTable(db);
+ insertVideoData(db);
+ }
+
+ @Override
+ public void verifyResult() {
+ assertEquals(0, mAlbumSet.getMediaItemCount());
+ assertEquals(1, mAlbumSet.getSubMediaSetCount());
+ assertEquals(1, mAlbumSet.getTotalMediaItemCount());
+ MediaSet sub = mAlbumSet.getSubMediaSet(0);
+ assertEquals(1, sub.getMediaItemCount());
+ assertEquals(0, sub.getSubMediaSetCount());
+ LocalMediaItem item = (LocalMediaItem) sub.getMediaItem(0, 1).get(0);
+ assertEquals(1, item.id);
+ assertEquals("VID_20100811_051413", item.caption);
+ assertEquals("video/mp4", item.mimeType);
+ assertEquals(11.0, item.latitude);
+ assertEquals(22.0, item.longitude);
+ assertEquals(0xD000, item.dateTakenInMs);
+ assertEquals(1281503663L, item.dateAddedInSec);
+ assertEquals(1281503662L, item.dateModifiedInSec);
+ assertEquals("/mnt/sdcard/DCIM/Camera/VID_20100811_051413.3gp",
+ item.filePath);
+ }
+ }
+
+ class TestMoreVideos extends TestLocalVideoAlbum {
+ @Override
+ public void prepareData(SQLiteDatabase db) {
+ // Albums are sorted by names, and items are sorted by
+ // dateTimeTaken (descending)
+ createVideoTable(db);
+ // bucket 0xB002
+ insertVideoData(db, 1000, 0xB000, "second"); // id 1
+ insertVideoData(db, 2000, 0xB000, "second"); // id 2
+ // bucket 0xB001
+ insertVideoData(db, 3000, 0xB001, "first"); // id 3
+ }
+
+ @Override
+ public void verifyResult() {
+ assertEquals(0, mAlbumSet.getMediaItemCount());
+ assertEquals(2, mAlbumSet.getSubMediaSetCount());
+ assertEquals(3, mAlbumSet.getTotalMediaItemCount());
+
+ MediaSet first = mAlbumSet.getSubMediaSet(0);
+ assertEquals(1, first.getMediaItemCount());
+ LocalMediaItem item = (LocalMediaItem) first.getMediaItem(0, 1).get(0);
+ assertEquals(3, item.id);
+ assertEquals(3000L, item.dateTakenInMs);
+
+ MediaSet second = mAlbumSet.getSubMediaSet(1);
+ assertEquals(2, second.getMediaItemCount());
+ item = (LocalMediaItem) second.getMediaItem(0, 1).get(0);
+ assertEquals(2, item.id);
+ assertEquals(2000L, item.dateTakenInMs);
+ item = (LocalMediaItem) second.getMediaItem(1, 1).get(0);
+ assertEquals(1, item.id);
+ assertEquals(1000L, item.dateTakenInMs);
+ }
+ }
+
+ static void createVideoTable(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE IF NOT EXISTS video (" +
+ "_id INTEGER PRIMARY KEY," +
+ "_data TEXT NOT NULL," +
+ "_display_name TEXT," +
+ "_size INTEGER," +
+ "mime_type TEXT," +
+ "date_added INTEGER," +
+ "date_modified INTEGER," +
+ "title TEXT," +
+ "duration INTEGER," +
+ "artist TEXT," +
+ "album TEXT," +
+ "resolution TEXT," +
+ "description TEXT," +
+ "isprivate INTEGER," + // for YouTube videos
+ "tags TEXT," + // for YouTube videos
+ "category TEXT," + // for YouTube videos
+ "language TEXT," + // for YouTube videos
+ "mini_thumb_data TEXT," +
+ "latitude DOUBLE," +
+ "longitude DOUBLE," +
+ "datetaken INTEGER," +
+ "mini_thumb_magic INTEGER" +
+ ");");
+ db.execSQL("ALTER TABLE video ADD COLUMN bucket_id TEXT;");
+ db.execSQL("ALTER TABLE video ADD COLUMN bucket_display_name TEXT");
+ }
+
+ static void insertVideoData(SQLiteDatabase db) {
+ insertVideoData(db, 0xD000, 0xB000, "name");
+ }
+
+ static void insertVideoData(SQLiteDatabase db, long dateTaken,
+ int bucketId, String bucketName) {
+ db.execSQL("INSERT INTO video (title, mime_type, latitude, longitude, "
+ + "datetaken, date_added, date_modified, bucket_id, "
+ + "bucket_display_name, _data, duration) "
+ + "VALUES ('VID_20100811_051413', 'video/mp4', 11, 22, "
+ + dateTaken + ", 1281503663, 1281503662, '" + bucketId + "', "
+ + "'" + bucketName + "', "
+ + "'/mnt/sdcard/DCIM/Camera/VID_20100811_051413.3gp', 2964)");
+ }
+
+ static GalleryAppStub newGalleryContext(SQLiteDatabase db, Looper mainLooper) {
+ MockContentResolver cr = new MockContentResolver();
+ ContentProvider cp = new DbContentProvider(db, cr);
+ cr.addProvider("media", cp);
+ return new GalleryAppMock(null, cr, mainLooper);
+ }
+}
+
+class DbContentProvider extends MockContentProvider {
+ private static final String TAG = "DbContentProvider";
+ private SQLiteDatabase mDatabase;
+ private ContentResolver mContentResolver;
+
+ DbContentProvider(SQLiteDatabase db, ContentResolver cr) {
+ mDatabase = db;
+ mContentResolver = cr;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder) {
+ // This is a simplified version extracted from MediaProvider.
+
+ String tableName = getTableName(uri);
+ if (tableName == null) return null;
+
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ qb.setTables(tableName);
+
+ String groupBy = null;
+ String limit = uri.getQueryParameter("limit");
+
+ if (uri.getQueryParameter("distinct") != null) {
+ qb.setDistinct(true);
+ }
+
+ Log.v(TAG, "query = " + qb.buildQuery(projection, selection,
+ selectionArgs, groupBy, null, sortOrder, limit));
+
+ if (selectionArgs != null) {
+ for (String s : selectionArgs) {
+ Log.v(TAG, " selectionArgs = " + s);
+ }
+ }
+
+ Cursor c = qb.query(mDatabase, projection, selection,
+ selectionArgs, groupBy, null, sortOrder, limit);
+
+ return c;
+ }
+
+ @Override
+ public int delete(Uri uri, String whereClause, String[] whereArgs) {
+ Log.v(TAG, "delete " + uri + "," + whereClause + "," + whereArgs[0]);
+ String tableName = getTableName(uri);
+ if (tableName == null) return 0;
+ int count = mDatabase.delete(tableName, whereClause, whereArgs);
+ mContentResolver.notifyChange(uri, null);
+ return count;
+ }
+
+ private String getTableName(Uri uri) {
+ String uriString = uri.toString();
+ if (uriString.startsWith("content://media/external/images/media")) {
+ return "images";
+ } else if (uriString.startsWith("content://media/external/video/media")) {
+ return "video";
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/tests/src/com/android/gallery3d/data/MediaSetTest.java b/tests/src/com/android/gallery3d/data/MediaSetTest.java
new file mode 100644
index 0000000..33dfe96
--- /dev/null
+++ b/tests/src/com/android/gallery3d/data/MediaSetTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2010 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.data;
+
+import com.android.gallery3d.app.GalleryApp;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+public class MediaSetTest extends AndroidTestCase {
+ @SuppressWarnings("unused")
+ private static final String TAG = "MediaSetTest";
+
+ @SmallTest
+ public void testComboAlbumSet() {
+ GalleryApp app = new GalleryAppMock(null, null, null);
+ Path.clearAll();
+ DataManager dataManager = app.getDataManager();
+
+ dataManager.addSource(new ComboSource(app));
+ dataManager.addSource(new MockSource(app));
+
+ MockSet set00 = new MockSet(Path.fromString("/mock/00"), dataManager, 0, 2000);
+ MockSet set01 = new MockSet(Path.fromString("/mock/01"), dataManager, 1, 3000);
+ MockSet set10 = new MockSet(Path.fromString("/mock/10"), dataManager, 2, 4000);
+ MockSet set11 = new MockSet(Path.fromString("/mock/11"), dataManager, 3, 5000);
+ MockSet set12 = new MockSet(Path.fromString("/mock/12"), dataManager, 4, 6000);
+
+ MockSet set0 = new MockSet(Path.fromString("/mock/0"), dataManager, 7, 7000);
+ set0.addMediaSet(set00);
+ set0.addMediaSet(set01);
+
+ MockSet set1 = new MockSet(Path.fromString("/mock/1"), dataManager, 8, 8000);
+ set1.addMediaSet(set10);
+ set1.addMediaSet(set11);
+ set1.addMediaSet(set12);
+
+ MediaSet combo = dataManager.getMediaSet("/combo/{/mock/0,/mock/1}");
+ assertEquals(5, combo.getSubMediaSetCount());
+ assertEquals(0, combo.getMediaItemCount());
+ assertEquals("/mock/00", combo.getSubMediaSet(0).getPath().toString());
+ assertEquals("/mock/01", combo.getSubMediaSet(1).getPath().toString());
+ assertEquals("/mock/10", combo.getSubMediaSet(2).getPath().toString());
+ assertEquals("/mock/11", combo.getSubMediaSet(3).getPath().toString());
+ assertEquals("/mock/12", combo.getSubMediaSet(4).getPath().toString());
+
+ assertEquals(10, combo.getTotalMediaItemCount());
+ }
+}
diff --git a/tests/src/com/android/gallery3d/data/MockItem.java b/tests/src/com/android/gallery3d/data/MockItem.java
new file mode 100644
index 0000000..2901979
--- /dev/null
+++ b/tests/src/com/android/gallery3d/data/MockItem.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2010 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.data;
+
+import com.android.gallery3d.util.ThreadPool.Job;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapRegionDecoder;
+
+public class MockItem extends MediaItem {
+ public MockItem(Path path) {
+ super(path, nextVersionNumber());
+ }
+
+ @Override
+ public Job<Bitmap> requestImage(int type) {
+ return null;
+ }
+
+ @Override
+ public Job<BitmapRegionDecoder> requestLargeImage() {
+ return null;
+ }
+
+ @Override
+ public String getMimeType() {
+ return null;
+ }
+
+ @Override
+ public int getWidth() {
+ return 0;
+ }
+
+ @Override
+ public int getHeight() {
+ return 0;
+ }
+}
diff --git a/tests/src/com/android/gallery3d/data/MockSet.java b/tests/src/com/android/gallery3d/data/MockSet.java
new file mode 100644
index 0000000..fa83c79
--- /dev/null
+++ b/tests/src/com/android/gallery3d/data/MockSet.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2010 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.data;
+
+import java.util.ArrayList;
+
+public class MockSet extends MediaSet {
+ ArrayList<MediaItem> mItems = new ArrayList<MediaItem>();
+ ArrayList<MediaSet> mSets = new ArrayList<MediaSet>();
+ Path mItemPath;
+
+ public MockSet(Path path, DataManager dataManager) {
+ super(path, nextVersionNumber());
+ mItemPath = Path.fromString("/mock/item");
+ }
+
+ public MockSet(Path path, DataManager dataManager,
+ int items, int item_id_start) {
+ this(path, dataManager);
+ for (int i = 0; i < items; i++) {
+ Path childPath = mItemPath.getChild(item_id_start + i);
+ mItems.add(new MockItem(childPath));
+ }
+ }
+
+ public void addMediaSet(MediaSet sub) {
+ mSets.add(sub);
+ }
+
+ @Override
+ public int getMediaItemCount() {
+ return mItems.size();
+ }
+
+ @Override
+ public ArrayList<MediaItem> getMediaItem(int start, int count) {
+ ArrayList<MediaItem> result = new ArrayList<MediaItem>();
+ int end = Math.min(start + count, mItems.size());
+
+ for (int i = start; i < end; i++) {
+ result.add(mItems.get(i));
+ }
+ return result;
+ }
+
+ @Override
+ public int getSubMediaSetCount() {
+ return mSets.size();
+ }
+
+ @Override
+ public MediaSet getSubMediaSet(int index) {
+ return mSets.get(index);
+ }
+
+ @Override
+ public int getTotalMediaItemCount() {
+ int result = mItems.size();
+ for (MediaSet s : mSets) {
+ result += s.getTotalMediaItemCount();
+ }
+ return result;
+ }
+
+ @Override
+ public String getName() {
+ return "Set " + mPath;
+ }
+
+ @Override
+ public long reload() {
+ return 0;
+ }
+}
diff --git a/tests/src/com/android/gallery3d/data/MockSource.java b/tests/src/com/android/gallery3d/data/MockSource.java
new file mode 100644
index 0000000..27ed4d0
--- /dev/null
+++ b/tests/src/com/android/gallery3d/data/MockSource.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 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.data;
+
+import com.android.gallery3d.app.GalleryApp;
+
+class MockSource extends MediaSource {
+ GalleryApp mApplication;
+ PathMatcher mMatcher;
+
+ private static final int MOCK_SET = 0;
+ private static final int MOCK_ITEM = 1;
+
+ public MockSource(GalleryApp context) {
+ super("mock");
+ mApplication = context;
+ mMatcher = new PathMatcher();
+ mMatcher.add("/mock/*", MOCK_SET);
+ mMatcher.add("/mock/item/*", MOCK_ITEM);
+ }
+
+ @Override
+ public MediaObject createMediaObject(Path path) {
+ MediaObject obj;
+ switch (mMatcher.match(path)) {
+ case MOCK_SET:
+ return new MockSet(path, mApplication.getDataManager());
+ case MOCK_ITEM:
+ return new MockItem(path);
+ default:
+ throw new RuntimeException("bad path: " + path);
+ }
+ }
+}
diff --git a/tests/src/com/android/gallery3d/data/PathTest.java b/tests/src/com/android/gallery3d/data/PathTest.java
new file mode 100644
index 0000000..b43d109
--- /dev/null
+++ b/tests/src/com/android/gallery3d/data/PathTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2010 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.data;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+public class PathTest extends AndroidTestCase {
+ @SuppressWarnings("unused")
+ private static final String TAG = "PathTest";
+
+ @SmallTest
+ public void testToString() {
+ Path p = Path.fromString("/hello/world");
+ assertEquals("/hello/world", p.toString());
+
+ p = Path.fromString("/a");
+ assertEquals("/a", p.toString());
+
+ p = Path.fromString("");
+ assertEquals("", p.toString());
+ }
+
+ @SmallTest
+ public void testSplit() {
+ Path p = Path.fromString("/hello/world");
+ String[] s = p.split();
+ assertEquals(2, s.length);
+ assertEquals("hello", s[0]);
+ assertEquals("world", s[1]);
+
+ p = Path.fromString("");
+ assertEquals(0, p.split().length);
+ }
+
+ @SmallTest
+ public void testPrefix() {
+ Path p = Path.fromString("/hello/world");
+ assertEquals("hello", p.getPrefix());
+
+ p = Path.fromString("");
+ assertEquals("", p.getPrefix());
+ }
+
+ @SmallTest
+ public void testGetChild() {
+ Path p = Path.fromString("/hello");
+ Path q = Path.fromString("/hello/world");
+ assertSame(q, p.getChild("world"));
+ Path r = q.getChild(17);
+ assertEquals("/hello/world/17", r.toString());
+ }
+
+ @SmallTest
+ public void testSplitSequence() {
+ String[] s = Path.splitSequence("{a,bb,ccc}");
+ assertEquals(3, s.length);
+ assertEquals("a", s[0]);
+ assertEquals("bb", s[1]);
+ assertEquals("ccc", s[2]);
+
+ s = Path.splitSequence("{a,{bb,ccc},d}");
+ assertEquals(3, s.length);
+ assertEquals("a", s[0]);
+ assertEquals("{bb,ccc}", s[1]);
+ assertEquals("d", s[2]);
+ }
+}
diff --git a/tests/src/com/android/gallery3d/data/RealDataTest.java b/tests/src/com/android/gallery3d/data/RealDataTest.java
new file mode 100644
index 0000000..526cfe3
--- /dev/null
+++ b/tests/src/com/android/gallery3d/data/RealDataTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2010 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.data;
+
+import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.picasasource.PicasaSource;
+
+import android.os.Looper;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+// This test reads real data directly and dump information out in the log.
+public class RealDataTest extends AndroidTestCase {
+ private static final String TAG = "RealDataTest";
+
+ private HashSet<Path> mUsedId = new HashSet<Path>();
+ private GalleryApp mApplication;
+ private DataManager mDataManager;
+
+ @LargeTest
+ public void testRealData() {
+ mUsedId.clear();
+ mApplication = new GalleryAppMock(
+ mContext,
+ mContext.getContentResolver(),
+ Looper.myLooper());
+ mDataManager = mApplication.getDataManager();
+ mDataManager.addSource(new LocalSource(mApplication));
+ mDataManager.addSource(new PicasaSource(mApplication));
+ new TestLocalImage().run();
+ new TestLocalVideo().run();
+ new TestPicasa().run();
+ }
+
+ class TestLocalImage {
+ public void run() {
+ MediaSet set = mDataManager.getMediaSet("/local/image");
+ set.reload();
+ Log.v(TAG, "LocalAlbumSet (Image)");
+ dumpMediaSet(set, "");
+ }
+ }
+
+ class TestLocalVideo {
+ public void run() {
+ MediaSet set = mDataManager.getMediaSet("/local/video");
+ set.reload();
+ Log.v(TAG, "LocalAlbumSet (Video)");
+ dumpMediaSet(set, "");
+ }
+ }
+
+ class TestPicasa implements Runnable {
+ public void run() {
+ MediaSet set = mDataManager.getMediaSet("/picasa");
+ set.reload();
+ Log.v(TAG, "PicasaAlbumSet");
+ dumpMediaSet(set, "");
+ }
+ }
+
+ void dumpMediaSet(MediaSet set, String prefix) {
+ Log.v(TAG, "getName() = " + set.getName());
+ Log.v(TAG, "getPath() = " + set.getPath());
+ Log.v(TAG, "getMediaItemCount() = " + set.getMediaItemCount());
+ Log.v(TAG, "getSubMediaSetCount() = " + set.getSubMediaSetCount());
+ Log.v(TAG, "getTotalMediaItemCount() = " + set.getTotalMediaItemCount());
+ assertNewId(set.getPath());
+ for (int i = 0, n = set.getSubMediaSetCount(); i < n; i++) {
+ MediaSet sub = set.getSubMediaSet(i);
+ Log.v(TAG, prefix + "got set " + i);
+ dumpMediaSet(sub, prefix + " ");
+ }
+ for (int i = 0, n = set.getMediaItemCount(); i < n; i += 10) {
+ ArrayList<MediaItem> list = set.getMediaItem(i, 10);
+ Log.v(TAG, prefix + "got item " + i + " (+" + list.size() + ")");
+ for (MediaItem item : list) {
+ dumpMediaItem(item, prefix + "..");
+ }
+ }
+ }
+
+ void dumpMediaItem(MediaItem item, String prefix) {
+ assertNewId(item.getPath());
+ Log.v(TAG, prefix + "getPath() = " + item.getPath());
+ }
+
+ void assertNewId(Path key) {
+ assertFalse(key + " has already appeared.", mUsedId.contains(key));
+ mUsedId.add(key);
+ }
+}
diff --git a/tests/src/com/android/gallery3d/exif/ExifDataTest.java b/tests/src/com/android/gallery3d/exif/ExifDataTest.java
new file mode 100644
index 0000000..142cc6b
--- /dev/null
+++ b/tests/src/com/android/gallery3d/exif/ExifDataTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.test.suitebuilder.annotation.SmallTest;
+import junit.framework.TestCase;
+import java.nio.ByteOrder;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ExifDataTest extends TestCase {
+ Map<Integer, ExifTag> mTestTags;
+ ExifInterface mInterface;
+ private ExifTag mVersionTag;
+ private ExifTag mGpsVersionTag;
+ private ExifTag mModelTag;
+ private ExifTag mDateTimeTag;
+ private ExifTag mCompressionTag;
+ private ExifTag mThumbnailFormatTag;
+ private ExifTag mLongitudeTag;
+ private ExifTag mShutterTag;
+ private ExifTag mInteropIndex;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mInterface = new ExifInterface();
+
+ // TYPE_UNDEFINED with 4 components
+ mVersionTag = mInterface.buildTag(ExifInterface.TAG_EXIF_VERSION, new byte[] {
+ 5, 4, 3, 2
+ });
+ // TYPE_UNSIGNED_BYTE with 4 components
+ mGpsVersionTag = mInterface.buildTag(ExifInterface.TAG_GPS_VERSION_ID, new byte[] {
+ 6, 7, 8, 9
+ });
+ // TYPE ASCII with arbitrary length
+ mModelTag = mInterface.buildTag(ExifInterface.TAG_MODEL, "helloworld");
+ // TYPE_ASCII with 20 components
+ mDateTimeTag = mInterface.buildTag(ExifInterface.TAG_DATE_TIME, "2013:02:11 20:20:20");
+ // TYPE_UNSIGNED_SHORT with 1 components
+ mCompressionTag = mInterface.buildTag(ExifInterface.TAG_COMPRESSION, 100);
+ // TYPE_UNSIGNED_LONG with 1 components
+ mThumbnailFormatTag =
+ mInterface.buildTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, 100);
+ // TYPE_UNSIGNED_RATIONAL with 3 components
+ mLongitudeTag = mInterface.buildTag(ExifInterface.TAG_GPS_LONGITUDE, new Rational[] {
+ new Rational(2, 2), new Rational(11, 11),
+ new Rational(102, 102)
+ });
+ // TYPE_RATIONAL with 1 components
+ mShutterTag = mInterface
+ .buildTag(ExifInterface.TAG_SHUTTER_SPEED_VALUE, new Rational(4, 6));
+ // TYPE_ASCII with arbitrary length
+ mInteropIndex = mInterface.buildTag(ExifInterface.TAG_INTEROPERABILITY_INDEX, "foo");
+
+ mTestTags = new HashMap<Integer, ExifTag>();
+
+ mTestTags.put(ExifInterface.TAG_EXIF_VERSION, mVersionTag);
+ mTestTags.put(ExifInterface.TAG_GPS_VERSION_ID, mGpsVersionTag);
+ mTestTags.put(ExifInterface.TAG_MODEL, mModelTag);
+ mTestTags.put(ExifInterface.TAG_DATE_TIME, mDateTimeTag);
+ mTestTags.put(ExifInterface.TAG_COMPRESSION, mCompressionTag);
+ mTestTags.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, mThumbnailFormatTag);
+ mTestTags.put(ExifInterface.TAG_GPS_LONGITUDE, mLongitudeTag);
+ mTestTags.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE, mShutterTag);
+ mTestTags.put(ExifInterface.TAG_INTEROPERABILITY_INDEX, mInteropIndex);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ mInterface = null;
+ mTestTags = null;
+ }
+
+ @SmallTest
+ public void testAddTag() {
+ ExifData exifData = new ExifData(ByteOrder.BIG_ENDIAN);
+
+ // Add all test tags
+ for (ExifTag t : mTestTags.values()) {
+ assertTrue(exifData.addTag(t) == null);
+ }
+
+ // Make sure no initial thumbnails
+ assertFalse(exifData.hasCompressedThumbnail());
+ assertFalse(exifData.hasUncompressedStrip());
+
+ // Check that we can set thumbnails
+ exifData.setStripBytes(3, new byte[] {
+ 1, 2, 3, 4, 5
+ });
+ assertTrue(exifData.hasUncompressedStrip());
+ exifData.setCompressedThumbnail(new byte[] {
+ 1
+ });
+ assertTrue(exifData.hasCompressedThumbnail());
+
+ // Check that we can clear thumbnails
+ exifData.clearThumbnailAndStrips();
+ assertFalse(exifData.hasCompressedThumbnail());
+ assertFalse(exifData.hasUncompressedStrip());
+
+ // Make sure ifds exist
+ for (int i : IfdData.getIfds()) {
+ assertTrue(exifData.getIfdData(i) != null);
+ }
+
+ // Get all test tags
+ List<ExifTag> allTags = exifData.getAllTags();
+ assertTrue(allTags != null);
+
+ // Make sure all test tags are in data
+ for (ExifTag t : mTestTags.values()) {
+ boolean check = false;
+ for (ExifTag i : allTags) {
+ if (t.equals(i)) {
+ check = true;
+ break;
+ }
+ }
+ assertTrue(check);
+ }
+
+ // Check if getting tags for a tid works
+ List<ExifTag> tidTags = exifData.getAllTagsForTagId(ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_SHUTTER_SPEED_VALUE));
+ assertTrue(tidTags.size() == 1);
+ assertTrue(tidTags.get(0).equals(mShutterTag));
+
+ // Check if getting tags for an ifd works
+ List<ExifTag> ifdTags = exifData.getAllTagsForIfd(IfdId.TYPE_IFD_INTEROPERABILITY);
+ assertTrue(ifdTags.size() == 1);
+ assertTrue(ifdTags.get(0).equals(mInteropIndex));
+
+ }
+}
diff --git a/tests/src/com/android/gallery3d/exif/ExifInterfaceTest.java b/tests/src/com/android/gallery3d/exif/ExifInterfaceTest.java
new file mode 100644
index 0000000..01b2a32
--- /dev/null
+++ b/tests/src/com/android/gallery3d/exif/ExifInterfaceTest.java
@@ -0,0 +1,533 @@
+/*
+ * Copyright (C) 2013 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.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import android.test.suitebuilder.annotation.MediumTest;
+
+import java.io.ByteArrayInputStream;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ExifInterfaceTest extends ExifXmlDataTestCase {
+
+ private File mTmpFile;
+ private List<Map<Short, List<String>>> mGroundTruth;
+ private ExifInterface mInterface;
+ private ExifTag mVersionTag;
+ private ExifTag mGpsVersionTag;
+ private ExifTag mModelTag;
+ private ExifTag mDateTimeTag;
+ private ExifTag mCompressionTag;
+ private ExifTag mThumbnailFormatTag;
+ private ExifTag mLongitudeTag;
+ private ExifTag mShutterTag;
+ Map<Integer, ExifTag> mTestTags;
+ Map<Integer, Integer> mTagDefinitions;
+
+ public ExifInterfaceTest(int imageRes, int xmlRes) {
+ super(imageRes, xmlRes);
+ }
+
+ public ExifInterfaceTest(String imagePath, String xmlPath) {
+ super(imagePath, xmlPath);
+ }
+
+ @MediumTest
+ public void testInterface() throws Exception {
+
+ InputStream imageInputStream = null;
+ try {
+ // Basic checks
+
+ // Check if bitmap is valid
+ byte[] imgData = Util.readToByteArray(getImageInputStream());
+ imageInputStream = new ByteArrayInputStream(imgData);
+ checkBitmap(imageInputStream);
+
+ // Check defines
+ int tag = ExifInterface.defineTag(1, (short) 0x0100);
+ assertTrue(getImageTitle(), tag == 0x00010100);
+ int tagDef = mInterface.getTagDefinition((short) 0x0100, IfdId.TYPE_IFD_0);
+ assertTrue(getImageTitle(), tagDef == 0x03040001);
+ int[] allowed = ExifInterface.getAllowedIfdsFromInfo(mInterface.getTagInfo().get(
+ ExifInterface.TAG_IMAGE_WIDTH));
+ assertTrue(getImageTitle(), allowed.length == 2 && allowed[0] == IfdId.TYPE_IFD_0
+ && allowed[1] == IfdId.TYPE_IFD_1);
+
+ // Check if there are any initial tags
+ assertTrue(getImageTitle(), mInterface.getAllTags() == null);
+
+ // ///////// Basic read/write testing
+
+ // Make sure we can read
+ imageInputStream = new ByteArrayInputStream(imgData);
+ mInterface.readExif(imageInputStream);
+
+ // Check tags against ground truth
+ checkTagsAgainstXml(mInterface.getAllTags());
+
+ // Make sure clearing Exif works
+ mInterface.clearExif();
+ assertTrue(getImageTitle(), mInterface.getAllTags() == null);
+
+ // Make sure setting tags works
+ mInterface.setTags(mTestTags.values());
+ checkTagsAgainstHash(mInterface.getAllTags(), mTestTags);
+
+ // Try writing over bitmap exif
+ ByteArrayOutputStream imgModified = new ByteArrayOutputStream();
+ mInterface.writeExif(imgData, imgModified);
+
+ // Check if bitmap is valid
+ byte[] imgData2 = imgModified.toByteArray();
+ imageInputStream = new ByteArrayInputStream(imgData2);
+ checkBitmap(imageInputStream);
+
+ // Make sure we get the same tags out
+ imageInputStream = new ByteArrayInputStream(imgData2);
+ mInterface.readExif(imageInputStream);
+ checkTagsAgainstHash(mInterface.getAllTags(), mTestTags);
+
+ // Reread original image
+ imageInputStream = new ByteArrayInputStream(imgData);
+ mInterface.readExif(imageInputStream);
+
+ // Write out with original exif
+ imgModified = new ByteArrayOutputStream();
+ mInterface.writeExif(imgData2, imgModified);
+
+ // Read back in exif and check tags
+ imgData2 = imgModified.toByteArray();
+ imageInputStream = new ByteArrayInputStream(imgData2);
+ mInterface.readExif(imageInputStream);
+ checkTagsAgainstXml(mInterface.getAllTags());
+
+ // Check if bitmap is valid
+ imageInputStream = new ByteArrayInputStream(imgData2);
+ checkBitmap(imageInputStream);
+
+ } catch (Exception e) {
+ throw new Exception(getImageTitle(), e);
+ } finally {
+ Util.closeSilently(imageInputStream);
+ }
+ }
+
+ @MediumTest
+ public void testInterfaceModify() throws Exception {
+
+ // TODO: This test is dependent on galaxy_nexus jpeg/xml file.
+ InputStream imageInputStream = null;
+ try {
+ // Check if bitmap is valid
+ byte[] imgData = Util.readToByteArray(getImageInputStream());
+ imageInputStream = new ByteArrayInputStream(imgData);
+ checkBitmap(imageInputStream);
+
+ // ///////// Exif modifier testing.
+
+ // Read exif and write to temp file
+ imageInputStream = new ByteArrayInputStream(imgData);
+ mInterface.readExif(imageInputStream);
+ mInterface.writeExif(imgData, mTmpFile.getPath());
+
+ // Check if bitmap is valid
+ imageInputStream = new FileInputStream(mTmpFile);
+ checkBitmap(imageInputStream);
+
+ // Create some tags to overwrite with
+ ArrayList<ExifTag> tags = new ArrayList<ExifTag>();
+ tags.add(mInterface.buildTag(ExifInterface.TAG_ORIENTATION,
+ ExifInterface.Orientation.RIGHT_TOP));
+ tags.add(mInterface.buildTag(ExifInterface.TAG_USER_COMMENT, "goooooooooooooooooogle"));
+
+ // Attempt to rewrite tags
+ assertTrue(getImageTitle(), mInterface.rewriteExif(mTmpFile.getPath(), tags));
+
+ imageInputStream.close();
+ // Check if bitmap is valid
+ imageInputStream = new FileInputStream(mTmpFile);
+ checkBitmap(imageInputStream);
+
+ // Read tags and check against xml
+ mInterface.readExif(mTmpFile.getPath());
+ for (ExifTag t : mInterface.getAllTags()) {
+ short tid = t.getTagId();
+ if (tid != ExifInterface.getTrueTagKey(ExifInterface.TAG_ORIENTATION)
+ && tid != ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT)) {
+ checkTagAgainstXml(t);
+ }
+ }
+ assertTrue(getImageTitle(), mInterface.getTagIntValue(ExifInterface.TAG_ORIENTATION)
+ .shortValue() == ExifInterface.Orientation.RIGHT_TOP);
+ String valString = mInterface.getTagStringValue(ExifInterface.TAG_USER_COMMENT);
+ assertTrue(getImageTitle(), valString.equals("goooooooooooooooooogle"));
+
+ // Test forced modify
+
+ // Create some tags to overwrite with
+ tags = new ArrayList<ExifTag>();
+ tags.add(mInterface.buildTag(ExifInterface.TAG_SOFTWARE, "magic super photomaker pro"));
+ tags.add(mInterface.buildTag(ExifInterface.TAG_USER_COMMENT, "noodles"));
+ tags.add(mInterface.buildTag(ExifInterface.TAG_ORIENTATION,
+ ExifInterface.Orientation.TOP_LEFT));
+
+ // Force rewrite tags
+ mInterface.forceRewriteExif(mTmpFile.getPath(), tags);
+
+ imageInputStream.close();
+ // Check if bitmap is valid
+ imageInputStream = new FileInputStream(mTmpFile);
+ checkBitmap(imageInputStream);
+
+ // Read tags and check against xml
+ mInterface.readExif(mTmpFile.getPath());
+ for (ExifTag t : mInterface.getAllTags()) {
+ short tid = t.getTagId();
+ if (!ExifInterface.isOffsetTag(tid)
+ && tid != ExifInterface.getTrueTagKey(ExifInterface.TAG_SOFTWARE)
+ && tid != ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT)) {
+ checkTagAgainstXml(t);
+ }
+ }
+ valString = mInterface.getTagStringValue(ExifInterface.TAG_SOFTWARE);
+ String compareString = "magic super photomaker pro\0";
+ assertTrue(getImageTitle(), valString.equals(compareString));
+ valString = mInterface.getTagStringValue(ExifInterface.TAG_USER_COMMENT);
+ assertTrue(getImageTitle(), valString.equals("noodles"));
+
+ } catch (Exception e) {
+ throw new Exception(getImageTitle(), e);
+ } finally {
+ Util.closeSilently(imageInputStream);
+ }
+ }
+
+ @MediumTest
+ public void testInterfaceDefines() throws Exception {
+
+ InputStream imageInputStream = null;
+ try {
+ // Check if bitmap is valid
+ byte[] imgData = Util.readToByteArray(getImageInputStream());
+ imageInputStream = new ByteArrayInputStream(imgData);
+ checkBitmap(imageInputStream);
+
+ // Set some tags.
+ mInterface.setTags(mTestTags.values());
+
+ // Check tag definitions against default
+ for (Integer i : mTestTags.keySet()) {
+ int check = mTagDefinitions.get(i).intValue();
+ int actual = mInterface.getTagInfo().get(i);
+ assertTrue(check == actual);
+ }
+
+ // Check defines
+ int tag1 = ExifInterface.defineTag(IfdId.TYPE_IFD_1, (short) 42);
+ int tag2 = ExifInterface.defineTag(IfdId.TYPE_IFD_INTEROPERABILITY, (short) 43);
+ assertTrue(tag1 == 0x0001002a);
+ assertTrue(tag2 == 0x0003002b);
+
+ // Define some non-standard tags
+ assertTrue(mInterface.setTagDefinition((short) 42, IfdId.TYPE_IFD_1,
+ ExifTag.TYPE_UNSIGNED_BYTE, (short) 16, new int[] {
+ IfdId.TYPE_IFD_1
+ }) == tag1);
+ assertTrue(mInterface.getTagInfo().get(tag1) == 0x02010010);
+ assertTrue(mInterface.setTagDefinition((short) 43, IfdId.TYPE_IFD_INTEROPERABILITY,
+ ExifTag.TYPE_ASCII, (short) 5, new int[] {
+ IfdId.TYPE_IFD_GPS, IfdId.TYPE_IFD_INTEROPERABILITY
+ }) == tag2);
+ assertTrue(mInterface.getTagInfo().get(tag2) == 0x18020005);
+
+ // Make sure these don't work
+ assertTrue(mInterface.setTagDefinition((short) 42, IfdId.TYPE_IFD_1,
+ ExifTag.TYPE_UNSIGNED_BYTE, (short) 16, new int[] {
+ IfdId.TYPE_IFD_0
+ }) == ExifInterface.TAG_NULL);
+ assertTrue(mInterface.setTagDefinition((short) 42, IfdId.TYPE_IFD_1, (short) 0,
+ (short) 16, new int[] {
+ IfdId.TYPE_IFD_1
+ }) == ExifInterface.TAG_NULL);
+ assertTrue(mInterface.setTagDefinition((short) 42, 5, ExifTag.TYPE_UNSIGNED_BYTE,
+ (short) 16, new int[] {
+ 5
+ }) == ExifInterface.TAG_NULL);
+ assertTrue(mInterface.setTagDefinition((short) 42, IfdId.TYPE_IFD_1,
+ ExifTag.TYPE_UNSIGNED_BYTE, (short) 16, new int[] {
+ -1
+ }) == ExifInterface.TAG_NULL);
+ assertTrue(mInterface.setTagDefinition((short) 43, IfdId.TYPE_IFD_GPS,
+ ExifTag.TYPE_ASCII, (short) 5, new int[] {
+ IfdId.TYPE_IFD_GPS
+ }) == ExifInterface.TAG_NULL);
+ assertTrue(mInterface.setTagDefinition((short) 43, IfdId.TYPE_IFD_0,
+ ExifTag.TYPE_ASCII, (short) 5, new int[] {
+ IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_GPS
+ }) == ExifInterface.TAG_NULL);
+
+ // Set some tags
+ mInterface.setTags(mTestTags.values());
+ checkTagsAgainstHash(mInterface.getAllTags(), mTestTags);
+
+ // Make some tags using new defines
+ ExifTag defTag0 = mInterface.buildTag(tag1, new byte[] {
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
+ });
+ assertTrue(defTag0 != null);
+ ExifTag defTag1 = mInterface.buildTag(tag2, "hihi");
+ assertTrue(defTag1 != null);
+ ExifTag defTag2 = mInterface.buildTag(tag2, IfdId.TYPE_IFD_GPS, "byte");
+ assertTrue(defTag2 != null);
+
+ // Make sure these don't work
+ ExifTag badTag = mInterface.buildTag(tag1, new byte[] {
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
+ });
+ assertTrue(badTag == null);
+ badTag = mInterface.buildTag(tag1, IfdId.TYPE_IFD_0, new byte[] {
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
+ });
+ assertTrue(badTag == null);
+ badTag = mInterface.buildTag(0x0002002a, new byte[] {
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
+ });
+ assertTrue(badTag == null);
+ badTag = mInterface.buildTag(tag2, new byte[] {
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17
+ });
+ assertTrue(badTag == null);
+
+ // Set the tags
+ assertTrue(mInterface.setTag(defTag0) == null);
+ assertTrue(mInterface.setTag(defTag1) == null);
+ assertTrue(mInterface.setTag(defTag2) == null);
+ assertTrue(mInterface.setTag(defTag0).equals(defTag0));
+ assertTrue(mInterface.setTag(null) == null);
+ assertTrue(mInterface.setTagValue(tag2, "yoyo") == true);
+ assertTrue(mInterface.setTagValue(tag2, "yaaarggg") == false);
+ assertTrue(mInterface.getTagStringValue(tag2).equals("yoyo\0"));
+
+ // Try writing over bitmap exif
+ ByteArrayOutputStream imgModified = new ByteArrayOutputStream();
+ mInterface.writeExif(imgData, imgModified);
+
+ // Check if bitmap is valid
+ byte[] imgData2 = imgModified.toByteArray();
+ imageInputStream = new ByteArrayInputStream(imgData2);
+ checkBitmap(imageInputStream);
+
+ // Read back in the tags
+ mInterface.readExif(imgData2);
+
+ // Check tags
+ for (ExifTag t : mInterface.getAllTags()) {
+ int tid = t.getTagId();
+ if (tid != ExifInterface.getTrueTagKey(tag1)
+ && tid != ExifInterface.getTrueTagKey(tag2)) {
+ checkTagAgainstHash(t, mTestTags);
+ }
+ }
+ assertTrue(Arrays.equals(mInterface.getTagByteValues(tag1), new byte[] {
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
+ }));
+ assertTrue(mInterface.getTagStringValue(tag2).equals("yoyo\0"));
+ assertTrue(mInterface.getTagStringValue(tag2, IfdId.TYPE_IFD_GPS).equals("byte\0"));
+
+ } catch (Exception e) {
+ throw new Exception(getImageTitle(), e);
+ } finally {
+ Util.closeSilently(imageInputStream);
+ }
+ }
+
+ @MediumTest
+ public void testInterfaceThumbnails() throws Exception {
+
+ InputStream imageInputStream = null;
+ try {
+ // Check if bitmap is valid
+ byte[] imgData = Util.readToByteArray(getImageInputStream());
+ imageInputStream = new ByteArrayInputStream(imgData);
+ checkBitmap(imageInputStream);
+
+ // Check thumbnails
+ mInterface.readExif(imgData);
+ Bitmap bmap = mInterface.getThumbnailBitmap();
+ assertTrue(getImageTitle(), bmap != null);
+
+ // Make a new thumbnail and set it
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+ opts.inSampleSize = 16;
+ Bitmap thumb = BitmapFactory.decodeByteArray(imgData, 0, imgData.length, opts);
+ assertTrue(getImageTitle(), thumb != null);
+ assertTrue(getImageTitle(), mInterface.setCompressedThumbnail(thumb) == true);
+
+ // Write out image
+ ByteArrayOutputStream outData = new ByteArrayOutputStream();
+ mInterface.writeExif(imgData, outData);
+
+ // Make sure bitmap is still valid
+ byte[] imgData2 = outData.toByteArray();
+ imageInputStream = new ByteArrayInputStream(imgData2);
+ checkBitmap(imageInputStream);
+
+ // Read in bitmap and make sure thumbnail is still valid
+ mInterface.readExif(imgData2);
+ bmap = mInterface.getThumbnailBitmap();
+ assertTrue(getImageTitle(), bmap != null);
+
+ } catch (Exception e) {
+ throw new Exception(getImageTitle(), e);
+ } finally {
+ Util.closeSilently(imageInputStream);
+ }
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mTmpFile = File.createTempFile("exif_test", ".jpg");
+ mGroundTruth = ExifXmlReader.readXml(getXmlParser());
+
+ mInterface = new ExifInterface();
+
+ // TYPE_UNDEFINED with 4 components
+ mVersionTag = mInterface.buildTag(ExifInterface.TAG_EXIF_VERSION, new byte[] {
+ 5, 4, 3, 2
+ });
+ // TYPE_UNSIGNED_BYTE with 4 components
+ mGpsVersionTag = mInterface.buildTag(ExifInterface.TAG_GPS_VERSION_ID, new byte[] {
+ 6, 7, 8, 9
+ });
+ // TYPE ASCII with arbitary length
+ mModelTag = mInterface.buildTag(ExifInterface.TAG_MODEL, "helloworld");
+ // TYPE_ASCII with 20 components
+ mDateTimeTag = mInterface.buildTag(ExifInterface.TAG_DATE_TIME, "2013:02:11 20:20:20");
+ // TYPE_UNSIGNED_SHORT with 1 components
+ mCompressionTag = mInterface.buildTag(ExifInterface.TAG_COMPRESSION, 100);
+ // TYPE_UNSIGNED_LONG with 1 components
+ mThumbnailFormatTag =
+ mInterface.buildTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, 100);
+ // TYPE_UNSIGNED_RATIONAL with 3 components
+ mLongitudeTag = mInterface.buildTag(ExifInterface.TAG_GPS_LONGITUDE, new Rational[] {
+ new Rational(2, 2), new Rational(11, 11),
+ new Rational(102, 102)
+ });
+ // TYPE_RATIONAL with 1 components
+ mShutterTag = mInterface
+ .buildTag(ExifInterface.TAG_SHUTTER_SPEED_VALUE, new Rational(4, 6));
+
+ mTestTags = new HashMap<Integer, ExifTag>();
+
+ mTestTags.put(ExifInterface.TAG_EXIF_VERSION, mVersionTag);
+ mTestTags.put(ExifInterface.TAG_GPS_VERSION_ID, mGpsVersionTag);
+ mTestTags.put(ExifInterface.TAG_MODEL, mModelTag);
+ mTestTags.put(ExifInterface.TAG_DATE_TIME, mDateTimeTag);
+ mTestTags.put(ExifInterface.TAG_COMPRESSION, mCompressionTag);
+ mTestTags.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, mThumbnailFormatTag);
+ mTestTags.put(ExifInterface.TAG_GPS_LONGITUDE, mLongitudeTag);
+ mTestTags.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE, mShutterTag);
+
+ mTagDefinitions = new HashMap<Integer, Integer>();
+ mTagDefinitions.put(ExifInterface.TAG_EXIF_VERSION, 0x04070004);
+ mTagDefinitions.put(ExifInterface.TAG_GPS_VERSION_ID, 0x10010004);
+ mTagDefinitions.put(ExifInterface.TAG_MODEL, 0x03020000);
+ mTagDefinitions.put(ExifInterface.TAG_DATE_TIME, 0x03020014);
+ mTagDefinitions.put(ExifInterface.TAG_COMPRESSION, 0x03030001);
+ mTagDefinitions.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, 0x02040001);
+ mTagDefinitions.put(ExifInterface.TAG_GPS_LONGITUDE, 0x100a0003);
+ mTagDefinitions.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE, 0x040a0001);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ mTmpFile.delete();
+ }
+
+ // Helper functions
+
+ private void checkTagAgainstXml(ExifTag tag) {
+ List<String> truth = mGroundTruth.get(tag.getIfd()).get(tag.getTagId());
+
+ if (truth == null) {
+ fail(String.format("Unknown Tag %02x", tag.getTagId()) + ", " + getImageTitle());
+ }
+
+ // No value from exiftool.
+ if (truth.contains(null))
+ return;
+
+ String dataString = Util.tagValueToString(tag).trim();
+ assertTrue(String.format("Tag %02x", tag.getTagId()) + ", " + getImageTitle()
+ + ": " + dataString,
+ truth.contains(dataString));
+ }
+
+ private void checkTagsAgainstXml(List<ExifTag> tags) {
+ for (ExifTag t : tags) {
+ checkTagAgainstXml(t);
+ }
+ }
+
+ private void checkTagAgainstHash(ExifTag tag, Map<Integer, ExifTag> testTags) {
+ int tagdef = mInterface.getTagDefinitionForTag(tag);
+ assertTrue(getImageTitle(), tagdef != ExifInterface.TAG_NULL);
+ ExifTag t = testTags.get(tagdef);
+ // Ignore offset tags & other special tags
+ if (!ExifInterface.sBannedDefines.contains(tag.getTagId())) {
+ assertTrue(getImageTitle(), t != null);
+ } else {
+ return;
+ }
+ if (t == tag)
+ return;
+ assertTrue(getImageTitle(), tag.equals(t));
+ assertTrue(getImageTitle(), tag.getDataType() == t.getDataType());
+ assertTrue(getImageTitle(), tag.getTagId() == t.getTagId());
+ assertTrue(getImageTitle(), tag.getIfd() == t.getIfd());
+ assertTrue(getImageTitle(), tag.getComponentCount() == t.getComponentCount());
+ }
+
+ private void checkTagsAgainstHash(List<ExifTag> tags, Map<Integer, ExifTag> testTags) {
+ for (ExifTag t : tags) {
+ checkTagAgainstHash(t, testTags);
+ }
+ }
+
+ private void checkBitmap(InputStream inputStream) throws IOException {
+ Bitmap bmp = BitmapFactory.decodeStream(inputStream);
+ assertTrue(getImageTitle(), bmp != null);
+ }
+
+}
diff --git a/tests/src/com/android/gallery3d/exif/ExifModifierTest.java b/tests/src/com/android/gallery3d/exif/ExifModifierTest.java
new file mode 100644
index 0000000..96f405e
--- /dev/null
+++ b/tests/src/com/android/gallery3d/exif/ExifModifierTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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.test.suitebuilder.annotation.MediumTest;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel.MapMode;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ExifModifierTest extends ExifXmlDataTestCase {
+
+ private File mTmpFile;
+ private List<Map<Short, List<String>>> mGroundTruth;
+ private ExifInterface mInterface;
+ private Map<Short, ExifTag> mTestTags;
+ ExifTag mVersionTag;
+ ExifTag mGpsVersionTag;
+ ExifTag mModelTag;
+ ExifTag mDateTimeTag;
+ ExifTag mCompressionTag;
+ ExifTag mThumbnailFormatTag;
+ ExifTag mLongitudeTag;
+ ExifTag mShutterTag;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mGroundTruth = ExifXmlReader.readXml(getXmlParser());
+ mTmpFile = File.createTempFile("exif_test", ".jpg");
+ FileOutputStream os = null;
+ InputStream is = getImageInputStream();
+ try {
+ os = new FileOutputStream(mTmpFile);
+ byte[] buf = new byte[1024];
+ int n;
+ while ((n = is.read(buf)) > 0) {
+ os.write(buf, 0, n);
+ }
+ } finally {
+ Util.closeSilently(os);
+ }
+
+ // TYPE_UNDEFINED with 4 components
+ mVersionTag = mInterface.buildTag(ExifInterface.TAG_EXIF_VERSION, new byte[] {
+ 1, 2, 3, 4
+ });
+ // TYPE_UNSIGNED_BYTE with 4 components
+ mGpsVersionTag = mInterface.buildTag(ExifInterface.TAG_GPS_VERSION_ID, new byte[] {
+ 4, 3, 2, 1
+ });
+ // TYPE ASCII with arbitary length
+ mModelTag = mInterface.buildTag(ExifInterface.TAG_MODEL, "end-of-the-world");
+ // TYPE_ASCII with 20 components
+ mDateTimeTag = mInterface.buildTag(ExifInterface.TAG_DATE_TIME, "2012:12:31 23:59:59");
+ // TYPE_UNSIGNED_SHORT with 1 components
+ mCompressionTag = mInterface.buildTag(ExifInterface.TAG_COMPRESSION, 100);
+ // TYPE_UNSIGNED_LONG with 1 components
+ mThumbnailFormatTag =
+ mInterface.buildTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, 100);
+ // TYPE_UNSIGNED_RATIONAL with 3 components
+ mLongitudeTag = mInterface.buildTag(ExifInterface.TAG_GPS_LONGITUDE, new Rational[] {
+ new Rational(1, 1), new Rational(10, 10),
+ new Rational(100, 100)
+ });
+ // TYPE_RATIONAL with 1 components
+ mShutterTag = mInterface
+ .buildTag(ExifInterface.TAG_SHUTTER_SPEED_VALUE, new Rational(1, 1));
+
+ mTestTags = new HashMap<Short, ExifTag>();
+
+ mTestTags.put(mVersionTag.getTagId(), mVersionTag);
+ mTestTags.put(mGpsVersionTag.getTagId(), mGpsVersionTag);
+ mTestTags.put(mModelTag.getTagId(), mModelTag);
+ mTestTags.put(mDateTimeTag.getTagId(), mDateTimeTag);
+ mTestTags.put(mCompressionTag.getTagId(), mCompressionTag);
+ mTestTags.put(mThumbnailFormatTag.getTagId(), mThumbnailFormatTag);
+ mTestTags.put(mLongitudeTag.getTagId(), mLongitudeTag);
+ mTestTags.put(mShutterTag.getTagId(), mShutterTag);
+ }
+
+ public ExifModifierTest(int imageRes, int xmlRes) {
+ super(imageRes, xmlRes);
+ mInterface = new ExifInterface();
+ }
+
+ public ExifModifierTest(String imagePath, String xmlPath) {
+ super(imagePath, xmlPath);
+ mInterface = new ExifInterface();
+ }
+
+ @MediumTest
+ public void testModify() throws Exception {
+ Map<Short, Boolean> results = new HashMap<Short, Boolean>();
+
+ RandomAccessFile file = null;
+ try {
+ file = new RandomAccessFile(mTmpFile, "rw");
+ MappedByteBuffer buf = file.getChannel().map(MapMode.READ_WRITE, 0, file.length());
+ for (ExifTag tag : mTestTags.values()) {
+ ExifModifier modifier = new ExifModifier(buf, mInterface);
+ modifier.modifyTag(tag);
+ boolean result = modifier.commit();
+ results.put(tag.getTagId(), result);
+ buf.force();
+ buf.position(0);
+
+ if (!result) {
+ List<String> value = mGroundTruth.get(tag.getIfd()).get(tag.getTagId());
+ assertTrue(String.format("Tag %x, ", tag.getTagId()) + getImageTitle(),
+ value == null || tag.getTagId() == ExifInterface.TAG_MODEL);
+ }
+ }
+ } finally {
+ Util.closeSilently(file);
+ }
+
+ // Parse the new file and check the result
+ InputStream is = null;
+ try {
+ is = new FileInputStream(mTmpFile);
+ ExifData data = new ExifReader(mInterface).read(is);
+ for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+ checkIfd(data.getIfdData(i), mGroundTruth.get(i), results);
+ }
+ } finally {
+ Util.closeSilently(is);
+ }
+
+ }
+
+ private void checkIfd(IfdData ifd, Map<Short, List<String>> ifdValue,
+ Map<Short, Boolean> results) {
+ if (ifd == null) {
+ assertEquals(getImageTitle(), 0, ifdValue.size());
+ return;
+ }
+ ExifTag[] tags = ifd.getAllTags();
+ for (ExifTag tag : tags) {
+ List<String> truth = ifdValue.get(tag.getTagId());
+ assertNotNull(String.format("Tag %x, ", tag.getTagId()) + getImageTitle(), truth);
+ if (truth.contains(null)) {
+ continue;
+ }
+
+ ExifTag newTag = mTestTags.get(tag.getTagId());
+ if (newTag != null
+ && results.get(tag.getTagId())) {
+ assertEquals(String.format("Tag %x, ", tag.getTagId()) + getImageTitle(),
+ Util.tagValueToString(newTag), Util.tagValueToString(tag));
+ } else {
+ assertTrue(String.format("Tag %x, ", tag.getTagId()) + getImageTitle(),
+ truth.contains(Util.tagValueToString(tag).trim()));
+ }
+ }
+ assertEquals(getImageTitle(), ifdValue.size(), tags.length);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ mTmpFile.delete();
+ }
+}
diff --git a/tests/src/com/android/gallery3d/exif/ExifOutputStreamTest.java b/tests/src/com/android/gallery3d/exif/ExifOutputStreamTest.java
new file mode 100644
index 0000000..151bdbc
--- /dev/null
+++ b/tests/src/com/android/gallery3d/exif/ExifOutputStreamTest.java
@@ -0,0 +1,198 @@
+/*
+ * 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.test.suitebuilder.annotation.MediumTest;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class ExifOutputStreamTest extends ExifXmlDataTestCase {
+
+ private File mTmpFile;
+
+ private ExifInterface mInterface;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mTmpFile = File.createTempFile("exif_test", ".jpg");
+ }
+
+ public ExifOutputStreamTest(int imgRes, int xmlRes) {
+ super(imgRes, xmlRes);
+ mInterface = new ExifInterface();
+ }
+
+ public ExifOutputStreamTest(String imgPath, String xmlPath) {
+ super(imgPath, xmlPath);
+ mInterface = new ExifInterface();
+ }
+
+ @MediumTest
+ public void testExifOutputStream() throws Exception {
+ InputStream imageInputStream = null;
+ InputStream exifInputStream = null;
+ FileInputStream reDecodeInputStream = null;
+ FileInputStream reParseInputStream = null;
+
+ InputStream dangerInputStream = null;
+ OutputStream dangerOutputStream = null;
+ try {
+ try {
+ byte[] imgData = Util.readToByteArray(getImageInputStream());
+ imageInputStream = new ByteArrayInputStream(imgData);
+ exifInputStream = new ByteArrayInputStream(imgData);
+
+ // Read the image data
+ Bitmap bmp = BitmapFactory.decodeStream(imageInputStream);
+ // The image is invalid
+ if (bmp == null) {
+ return;
+ }
+
+ // Read exif data
+ ExifData exifData = new ExifReader(mInterface).read(exifInputStream);
+
+ // Encode the image with the exif data
+ FileOutputStream outputStream = new FileOutputStream(mTmpFile);
+ ExifOutputStream exifOutputStream = new ExifOutputStream(outputStream, mInterface);
+ exifOutputStream.setExifData(exifData);
+ bmp.compress(Bitmap.CompressFormat.JPEG, 90, exifOutputStream);
+ exifOutputStream.close();
+ exifOutputStream = null;
+
+ // Re-decode the temp file and check the data.
+ reDecodeInputStream = new FileInputStream(mTmpFile);
+ Bitmap decodedBmp = BitmapFactory.decodeStream(reDecodeInputStream);
+ assertNotNull(getImageTitle(), decodedBmp);
+ reDecodeInputStream.close();
+
+ // Re-parse the temp file the check EXIF tag
+ reParseInputStream = new FileInputStream(mTmpFile);
+ ExifData reExifData = new ExifReader(mInterface).read(reParseInputStream);
+ assertEquals(getImageTitle(), exifData, reExifData);
+ reParseInputStream.close();
+
+ // Try writing exif to file with existing exif.
+ dangerOutputStream = (OutputStream) new FileOutputStream(mTmpFile);
+ exifOutputStream = new ExifOutputStream(dangerOutputStream, mInterface);
+ exifOutputStream.setExifData(exifData);
+ exifOutputStream.write(imgData);
+ // exifOutputStream.write(strippedImgData);
+ exifOutputStream.close();
+ exifOutputStream = null;
+
+ // Make sure it still can be parsed into a bitmap.
+ dangerInputStream = (InputStream) new FileInputStream(mTmpFile);
+ decodedBmp = null;
+ decodedBmp = BitmapFactory.decodeStream(dangerInputStream);
+ assertNotNull(getImageTitle(), decodedBmp);
+ dangerInputStream.close();
+ dangerInputStream = null;
+
+ // Make sure exif is still well-formatted.
+ dangerInputStream = (InputStream) new FileInputStream(mTmpFile);
+ reExifData = null;
+ reExifData = new ExifReader(mInterface).read(dangerInputStream);
+ assertEquals(getImageTitle(), exifData, reExifData);
+ dangerInputStream.close();
+ dangerInputStream = null;
+
+ } finally {
+ Util.closeSilently(imageInputStream);
+ Util.closeSilently(exifInputStream);
+ Util.closeSilently(reDecodeInputStream);
+ Util.closeSilently(reParseInputStream);
+
+ Util.closeSilently(dangerInputStream);
+ Util.closeSilently(dangerOutputStream);
+ }
+ } catch (Exception e) {
+ throw new Exception(getImageTitle(), e);
+ }
+ }
+
+ @MediumTest
+ public void testOutputSpeed() throws Exception {
+ final String LOGTAG = "testOutputSpeed";
+ InputStream imageInputStream = null;
+ OutputStream imageOutputStream = null;
+ try {
+ try {
+ imageInputStream = getImageInputStream();
+ // Read the image data
+ Bitmap bmp = BitmapFactory.decodeStream(imageInputStream);
+ // The image is invalid
+ if (bmp == null) {
+ return;
+ }
+ imageInputStream.close();
+ int nLoops = 20;
+ long totalReadDuration = 0;
+ long totalWriteDuration = 0;
+ for (int i = 0; i < nLoops; i++) {
+ imageInputStream = reopenFileStream();
+ // Read exif data
+ long startTime = System.nanoTime();
+ ExifData exifData = new ExifReader(mInterface).read(imageInputStream);
+ long endTime = System.nanoTime();
+ long duration = endTime - startTime;
+ totalReadDuration += duration;
+ Log.v(LOGTAG, " read time: " + duration);
+ imageInputStream.close();
+
+ // Encode the image with the exif data
+ imageOutputStream = (OutputStream) new FileOutputStream(mTmpFile);
+ ExifOutputStream exifOutputStream = new ExifOutputStream(imageOutputStream,
+ mInterface);
+ exifOutputStream.setExifData(exifData);
+ startTime = System.nanoTime();
+ bmp.compress(Bitmap.CompressFormat.JPEG, 90, exifOutputStream);
+ endTime = System.nanoTime();
+ duration = endTime - startTime;
+ totalWriteDuration += duration;
+ Log.v(LOGTAG, " write time: " + duration);
+ exifOutputStream.close();
+ }
+ Log.v(LOGTAG, "======================= normal");
+ Log.v(LOGTAG, "avg read time: " + totalReadDuration / nLoops);
+ Log.v(LOGTAG, "avg write time: " + totalWriteDuration / nLoops);
+ Log.v(LOGTAG, "=======================");
+ } finally {
+ Util.closeSilently(imageInputStream);
+ Util.closeSilently(imageOutputStream);
+ }
+ } catch (Exception e) {
+ throw new Exception(getImageTitle(), e);
+ }
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ mTmpFile.delete();
+ }
+}
diff --git a/tests/src/com/android/gallery3d/exif/ExifParserTest.java b/tests/src/com/android/gallery3d/exif/ExifParserTest.java
new file mode 100644
index 0000000..247ea02
--- /dev/null
+++ b/tests/src/com/android/gallery3d/exif/ExifParserTest.java
@@ -0,0 +1,243 @@
+/*
+ * 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.test.suitebuilder.annotation.MediumTest;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import java.util.List;
+import java.util.Map;
+
+public class ExifParserTest extends ExifXmlDataTestCase {
+ private static final String TAG = "ExifParserTest";
+
+ private ExifInterface mInterface;
+
+ public ExifParserTest(int imgRes, int xmlRes) {
+ super(imgRes, xmlRes);
+ mInterface = new ExifInterface();
+ }
+
+ public ExifParserTest(String imgPath, String xmlPath) {
+ super(imgPath, xmlPath);
+ mInterface = new ExifInterface();
+ }
+
+ private List<Map<Short, List<String>>> mGroundTruth;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mGroundTruth = ExifXmlReader.readXml(getXmlParser());
+ }
+
+ @MediumTest
+ public void testParse() throws Exception {
+ try {
+ ExifParser parser = ExifParser.parse(getImageInputStream(), mInterface);
+ int event = parser.next();
+ while (event != ExifParser.EVENT_END) {
+ switch (event) {
+ case ExifParser.EVENT_START_OF_IFD:
+ break;
+ case ExifParser.EVENT_NEW_TAG:
+ ExifTag tag = parser.getTag();
+ if (!tag.hasValue()) {
+ parser.registerForTagValue(tag);
+ } else {
+ checkTag(tag);
+ }
+ break;
+ case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
+ tag = parser.getTag();
+ if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) {
+ byte[] buf = new byte[tag.getComponentCount()];
+ parser.read(buf);
+ assertTrue(TAG, tag.setValue(buf));
+ }
+ checkTag(tag);
+ break;
+ }
+ event = parser.next();
+ }
+ } catch (Exception e) {
+ throw new Exception(getImageTitle(), e);
+ }
+ }
+
+ private void checkTag(ExifTag tag) {
+ List<String> truth = mGroundTruth.get(tag.getIfd()).get(tag.getTagId());
+
+ if (truth == null) {
+ fail(String.format("Unknown Tag %02x", tag.getTagId()) + ", " + getImageTitle());
+ }
+
+ // No value from exiftool.
+ if (truth.contains(null)) {
+ return;
+ }
+
+ String dataString = Util.tagValueToString(tag).trim();
+ assertTrue(String.format("Tag %02x", tag.getTagId()) + ", " + getImageTitle()
+ + ": " + dataString,
+ truth.contains(dataString));
+ }
+
+ private void parseOneIfd(int ifd, int options) throws Exception {
+ try {
+ Map<Short, List<String>> expectedResult = mGroundTruth.get(ifd);
+ int numOfTag = 0;
+ ExifParser parser = ExifParser.parse(getImageInputStream(), options, mInterface);
+ int event = parser.next();
+ while (event != ExifParser.EVENT_END) {
+ switch (event) {
+ case ExifParser.EVENT_START_OF_IFD:
+ assertEquals(getImageTitle(), ifd, parser.getCurrentIfd());
+ break;
+ case ExifParser.EVENT_NEW_TAG:
+ ExifTag tag = parser.getTag();
+ numOfTag++;
+ if (tag.hasValue()) {
+ checkTag(tag);
+ } else {
+ parser.registerForTagValue(tag);
+ }
+ break;
+ case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
+ tag = parser.getTag();
+ if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) {
+ byte[] buf = new byte[tag.getComponentCount()];
+ parser.read(buf);
+ tag.setValue(buf);
+ }
+ checkTag(tag);
+ break;
+ case ExifParser.EVENT_COMPRESSED_IMAGE:
+ case ExifParser.EVENT_UNCOMPRESSED_STRIP:
+ fail("Invalid Event type: " + event + ", " + getImageTitle());
+ break;
+ }
+ event = parser.next();
+ }
+ assertEquals(getImageTitle(), ExifXmlReader.getTrueTagNumber(expectedResult), numOfTag);
+ } catch (Exception e) {
+ throw new Exception(getImageTitle(), e);
+ }
+ }
+
+ @MediumTest
+ public void testOnlyExifIfd() throws Exception {
+ parseOneIfd(IfdId.TYPE_IFD_EXIF, ExifParser.OPTION_IFD_EXIF);
+ }
+
+ @MediumTest
+ public void testOnlyIfd0() throws Exception {
+ parseOneIfd(IfdId.TYPE_IFD_0, ExifParser.OPTION_IFD_0);
+ }
+
+ @MediumTest
+ public void testOnlyIfd1() throws Exception {
+ parseOneIfd(IfdId.TYPE_IFD_1, ExifParser.OPTION_IFD_1);
+ }
+
+ @MediumTest
+ public void testOnlyInteroperabilityIfd() throws Exception {
+ parseOneIfd(IfdId.TYPE_IFD_INTEROPERABILITY, ExifParser.OPTION_IFD_INTEROPERABILITY);
+ }
+
+ @MediumTest
+ public void testOnlyReadSomeTag() throws Exception {
+ // Do not do this test if there is no model tag.
+ if (mGroundTruth.get(IfdId.TYPE_IFD_0).get(ExifInterface.TAG_MODEL) == null) {
+ return;
+ }
+
+ try {
+ ExifParser parser = ExifParser.parse(getImageInputStream(), ExifParser.OPTION_IFD_0,
+ mInterface);
+ int event = parser.next();
+ boolean isTagFound = false;
+ while (event != ExifParser.EVENT_END) {
+ switch (event) {
+ case ExifParser.EVENT_START_OF_IFD:
+ assertEquals(getImageTitle(), IfdId.TYPE_IFD_0, parser.getCurrentIfd());
+ break;
+ case ExifParser.EVENT_NEW_TAG:
+ ExifTag tag = parser.getTag();
+ if (tag.getTagId() == ExifInterface.TAG_MODEL) {
+ if (tag.hasValue()) {
+ isTagFound = true;
+ checkTag(tag);
+ } else {
+ parser.registerForTagValue(tag);
+ }
+ parser.skipRemainingTagsInCurrentIfd();
+ }
+ break;
+ case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
+ tag = parser.getTag();
+ assertEquals(getImageTitle(), ExifInterface.TAG_MODEL, tag.getTagId());
+ checkTag(tag);
+ isTagFound = true;
+ break;
+ }
+ event = parser.next();
+ }
+ assertTrue(getImageTitle(), isTagFound);
+ } catch (Exception e) {
+ throw new Exception(getImageTitle(), e);
+ }
+ }
+
+ @MediumTest
+ public void testReadThumbnail() throws Exception {
+ try {
+ ExifParser parser = ExifParser.parse(getImageInputStream(),
+ ExifParser.OPTION_IFD_1 | ExifParser.OPTION_THUMBNAIL, mInterface);
+
+ int event = parser.next();
+ Bitmap bmp = null;
+ boolean mIsContainCompressedImage = false;
+ while (event != ExifParser.EVENT_END) {
+ switch (event) {
+ case ExifParser.EVENT_NEW_TAG:
+ ExifTag tag = parser.getTag();
+ if (tag.getTagId() == ExifInterface.TAG_COMPRESSION) {
+ if (tag.getValueAt(0) == ExifInterface.Compression.JPEG) {
+ mIsContainCompressedImage = true;
+ }
+ }
+ break;
+ case ExifParser.EVENT_COMPRESSED_IMAGE:
+ int imageSize = parser.getCompressedImageSize();
+ byte buf[] = new byte[imageSize];
+ parser.read(buf);
+ bmp = BitmapFactory.decodeByteArray(buf, 0, imageSize);
+ break;
+ }
+ event = parser.next();
+ }
+ if (mIsContainCompressedImage) {
+ assertNotNull(getImageTitle(), bmp);
+ }
+ } catch (Exception e) {
+ throw new Exception(getImageTitle(), e);
+ }
+ }
+}
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..4b5c029
--- /dev/null
+++ b/tests/src/com/android/gallery3d/exif/ExifReaderTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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.test.suitebuilder.annotation.MediumTest;
+
+import android.graphics.BitmapFactory;
+
+import java.util.List;
+import java.util.Map;
+
+public class ExifReaderTest extends ExifXmlDataTestCase {
+ private static final String TAG = "ExifReaderTest";
+
+ private ExifInterface mInterface;
+ private List<Map<Short, List<String>>> mGroundTruth;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mGroundTruth = ExifXmlReader.readXml(getXmlParser());
+ }
+
+ public ExifReaderTest(int imgRes, int xmlRes) {
+ super(imgRes, xmlRes);
+ mInterface = new ExifInterface();
+ }
+
+ public ExifReaderTest(String imgPath, String xmlPath) {
+ super(imgPath, xmlPath);
+ mInterface = new ExifInterface();
+ }
+
+ @MediumTest
+ public void testRead() throws Exception {
+ try {
+ ExifReader reader = new ExifReader(mInterface);
+ ExifData exifData = reader.read(getImageInputStream());
+ for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+ checkIfd(exifData.getIfdData(i), mGroundTruth.get(i));
+ }
+ checkThumbnail(exifData);
+ } catch (Exception e) {
+ throw new Exception(getImageTitle(), e);
+ }
+ }
+
+ private void checkThumbnail(ExifData exifData) {
+ Map<Short, List<String>> ifd1Truth = mGroundTruth.get(IfdId.TYPE_IFD_1);
+
+ List<String> typeTagValue = ifd1Truth.get(ExifInterface.TAG_COMPRESSION);
+ if (typeTagValue == null)
+ return;
+
+ IfdData ifd1 = exifData.getIfdData(IfdId.TYPE_IFD_1);
+ if (ifd1 == null)
+ fail(getImageTitle() + ": failed to find IFD1");
+
+ String typeTagTruth = typeTagValue.get(0);
+
+ int type = (int) ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_COMPRESSION))
+ .getValueAt(0);
+
+ if (String.valueOf(ExifInterface.Compression.JPEG).equals(typeTagTruth)) {
+ assertTrue(getImageTitle(), type == ExifInterface.Compression.JPEG);
+ assertTrue(getImageTitle(), exifData.hasCompressedThumbnail());
+ byte[] thumbnail = exifData.getCompressedThumbnail();
+ assertTrue(getImageTitle(),
+ BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length) != null);
+ } else if (String.valueOf(ExifInterface.Compression.UNCOMPRESSION).equals(typeTagTruth)) {
+ assertTrue(getImageTitle(), type == ExifInterface.Compression.UNCOMPRESSION);
+ // Try to check the strip count with the formula provided by EXIF spec.
+ int planarType = ExifInterface.PlanarConfiguration.CHUNKY;
+ ExifTag planarTag = ifd1.getTag(ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_PLANAR_CONFIGURATION));
+ if (planarTag != null) {
+ planarType = (int) planarTag.getValueAt(0);
+ }
+
+ if (!ifd1Truth.containsKey(ExifInterface.TAG_IMAGE_LENGTH) ||
+ !ifd1Truth.containsKey(ExifInterface.TAG_ROWS_PER_STRIP)) {
+ return;
+ }
+
+ ExifTag heightTag = ifd1.getTag(ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_IMAGE_LENGTH));
+ ExifTag rowPerStripTag = ifd1.getTag(ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_ROWS_PER_STRIP));
+
+ // Fail the test if required tags are missing
+ if (heightTag == null || rowPerStripTag == null) {
+ fail(getImageTitle());
+ }
+
+ int imageLength = (int) heightTag.getValueAt(0);
+ int rowsPerStrip = (int) rowPerStripTag.getValueAt(0);
+ int stripCount = ifd1.getTag(
+ ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS))
+ .getComponentCount();
+
+ if (planarType == ExifInterface.PlanarConfiguration.CHUNKY) {
+ assertTrue(getImageTitle(),
+ stripCount == (imageLength + rowsPerStrip - 1) / rowsPerStrip);
+ } else {
+ if (!ifd1Truth.containsKey(ExifInterface.TAG_SAMPLES_PER_PIXEL)) {
+ return;
+ }
+ ExifTag samplePerPixelTag = ifd1.getTag(ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_SAMPLES_PER_PIXEL));
+ int samplePerPixel = (int) samplePerPixelTag.getValueAt(0);
+ assertTrue(getImageTitle(),
+ stripCount ==
+ (imageLength + rowsPerStrip - 1) / rowsPerStrip * samplePerPixel);
+ }
+
+ if (!ifd1Truth.containsKey(ExifInterface.TAG_STRIP_BYTE_COUNTS)) {
+ return;
+ }
+ ExifTag byteCountTag = ifd1.getTag(ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
+ short byteCountDataType = byteCountTag.getDataType();
+ for (int i = 0; i < stripCount; i++) {
+ if (byteCountDataType == ExifTag.TYPE_UNSIGNED_SHORT) {
+ assertEquals(getImageTitle(),
+ byteCountTag.getValueAt(i), exifData.getStrip(i).length);
+ } else {
+ assertEquals(getImageTitle(),
+ byteCountTag.getValueAt(i), exifData.getStrip(i).length);
+ }
+ }
+ }
+ }
+
+ private void checkIfd(IfdData ifd, Map<Short, List<String>> ifdValue) {
+ if (ifd == null) {
+ assertEquals(getImageTitle(), 0, ifdValue.size());
+ return;
+ }
+ ExifTag[] tags = ifd.getAllTags();
+ for (ExifTag tag : tags) {
+ List<String> truth = ifdValue.get(tag.getTagId());
+ assertNotNull(String.format("Tag %x, ", tag.getTagId()) + getImageTitle(), truth);
+ if (truth.contains(null)) {
+ continue;
+ }
+ assertTrue(String.format("Tag %x, ", tag.getTagId()) + getImageTitle(),
+ truth.contains(Util.tagValueToString(tag).trim()));
+ }
+ assertEquals(getImageTitle(), ifdValue.size(), tags.length);
+ }
+}
diff --git a/tests/src/com/android/gallery3d/exif/ExifTagTest.java b/tests/src/com/android/gallery3d/exif/ExifTagTest.java
new file mode 100644
index 0000000..e6a41ec
--- /dev/null
+++ b/tests/src/com/android/gallery3d/exif/ExifTagTest.java
@@ -0,0 +1,219 @@
+/*
+ * 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.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ExifTagTest extends TestCase {
+
+ private static long MAX_UNSIGNED_LONG = (1L << 32) - 1;
+ private static int MAX_LONG = Integer.MAX_VALUE;
+ private static int MIN_LONG = Integer.MIN_VALUE;
+
+ Map<Integer, ExifTag> mTestTags;
+ ExifInterface mInterface;
+ private ExifTag mVersionTag;
+ private ExifTag mGpsVersionTag;
+ private ExifTag mModelTag;
+ private ExifTag mDateTimeTag;
+ private ExifTag mCompressionTag;
+ private ExifTag mThumbnailFormatTag;
+ private ExifTag mLongitudeTag;
+ private ExifTag mShutterTag;
+ private ExifTag mInteropIndex;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mInterface = new ExifInterface();
+
+ // TYPE_UNDEFINED with 4 components
+ mVersionTag = mInterface.buildTag(ExifInterface.TAG_EXIF_VERSION, new byte[] {
+ 5, 4, 3, 2
+ });
+ // TYPE_UNSIGNED_BYTE with 4 components
+ mGpsVersionTag = mInterface.buildTag(ExifInterface.TAG_GPS_VERSION_ID, new byte[] {
+ 6, 7, 8, 9
+ });
+ // TYPE ASCII with arbitrary length
+ mModelTag = mInterface.buildTag(ExifInterface.TAG_MODEL, "helloworld");
+ // TYPE_ASCII with 20 components
+ mDateTimeTag = mInterface.buildTag(ExifInterface.TAG_DATE_TIME, "2013:02:11 20:20:20");
+ // TYPE_UNSIGNED_SHORT with 1 components
+ mCompressionTag = mInterface.buildTag(ExifInterface.TAG_COMPRESSION, 100);
+ // TYPE_UNSIGNED_LONG with 1 components
+ mThumbnailFormatTag =
+ mInterface.buildTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, 100);
+ // TYPE_UNSIGNED_RATIONAL with 3 components
+ mLongitudeTag = mInterface.buildTag(ExifInterface.TAG_GPS_LONGITUDE, new Rational[] {
+ new Rational(2, 2), new Rational(11, 11),
+ new Rational(102, 102)
+ });
+ // TYPE_RATIONAL with 1 components
+ mShutterTag = mInterface
+ .buildTag(ExifInterface.TAG_SHUTTER_SPEED_VALUE, new Rational(4, 6));
+ // TYPE_ASCII with arbitrary length
+ mInteropIndex = mInterface.buildTag(ExifInterface.TAG_INTEROPERABILITY_INDEX, "foo");
+
+ mTestTags = new HashMap<Integer, ExifTag>();
+
+ mTestTags.put(ExifInterface.TAG_EXIF_VERSION, mVersionTag);
+ mTestTags.put(ExifInterface.TAG_GPS_VERSION_ID, mGpsVersionTag);
+ mTestTags.put(ExifInterface.TAG_MODEL, mModelTag);
+ mTestTags.put(ExifInterface.TAG_DATE_TIME, mDateTimeTag);
+ mTestTags.put(ExifInterface.TAG_COMPRESSION, mCompressionTag);
+ mTestTags.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, mThumbnailFormatTag);
+ mTestTags.put(ExifInterface.TAG_GPS_LONGITUDE, mLongitudeTag);
+ mTestTags.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE, mShutterTag);
+ mTestTags.put(ExifInterface.TAG_INTEROPERABILITY_INDEX, mInteropIndex);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ mInterface = null;
+ mTestTags = null;
+ }
+
+ @SmallTest
+ public void testValueType() {
+ for (ExifTag tag : mTestTags.values()) {
+ assertTrue(tag != null);
+ int count = tag.getComponentCount();
+ int intBuf[] = new int[count];
+ long longBuf[] = new long[count];
+ byte byteBuf[] = new byte[count];
+ Rational rationalBuf[] = new Rational[count];
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < count; i++) {
+ intBuf[i] = 0;
+ longBuf[i] = 0;
+ byteBuf[i] = 0;
+ rationalBuf[i] = new Rational(0, 0);
+ // The string size should equal to component count - 1
+ if (i != count - 1) {
+ sb.append("*");
+ } else {
+ sb.append("\0");
+ }
+ }
+ String strBuf = sb.toString();
+
+ checkTypeByte(tag, byteBuf);
+ checkTypeAscii(tag, strBuf);
+ checkTypeUnsignedShort(tag, intBuf);
+ checkTypeUnsignedLong(tag, intBuf, longBuf);
+ checkTypeLong(tag, intBuf);
+ checkTypeRational(tag, rationalBuf);
+ checkTypeUnsignedRational(tag, rationalBuf);
+ }
+ }
+
+ private void checkTypeByte(ExifTag tag, byte[] buf) {
+ short type = tag.getDataType();
+ assertFalse("\nTag: " + tag.toString(), tag.setValue(buf)
+ ^ (type == ExifTag.TYPE_UNDEFINED || type == ExifTag.TYPE_UNSIGNED_BYTE));
+ }
+
+ private void checkTypeAscii(ExifTag tag, String str) {
+ short type = tag.getDataType();
+ assertFalse("\nTag: " + tag.toString(), tag.setValue(str)
+ ^ (type == ExifTag.TYPE_ASCII || type == ExifTag.TYPE_UNDEFINED));
+ }
+
+ private void checkTypeUnsignedShort(ExifTag tag, int[] intBuf) {
+ short type = tag.getDataType();
+ assertFalse("\nTag: " + tag.toString(),
+ tag.setValue(intBuf)
+ ^ (type == ExifTag.TYPE_UNSIGNED_SHORT
+ || type == ExifTag.TYPE_UNSIGNED_LONG
+ || type == ExifTag.TYPE_LONG));
+ }
+
+ private void checkTypeUnsignedLong(ExifTag tag, int[] intBuf, long[] longBuf) {
+
+ // Test value only for unsigned long.
+ int count = intBuf.length;
+ intBuf[count - 1] = MAX_LONG;
+ tag.setValue(intBuf);
+ longBuf[count - 1] = MAX_UNSIGNED_LONG;
+
+ assertFalse("\nTag: " + tag.toString(), tag.setValue(longBuf)
+ ^ (tag.getDataType() == ExifTag.TYPE_UNSIGNED_LONG));
+
+ intBuf[count - 1] = 0;
+ // Test invalid value for all type.
+ longBuf[count - 1] = MAX_UNSIGNED_LONG + 1;
+ assertFalse(tag.setValue(longBuf));
+ longBuf[count - 1] = 0;
+ }
+
+ private void checkTypeLong(ExifTag tag, int[] intBuf) {
+ int count = intBuf.length;
+ intBuf[count - 1] = MAX_LONG;
+ tag.setValue(intBuf);
+ intBuf[count - 1] = MIN_LONG;
+
+ assertFalse("\nTag: " + tag.toString(), tag.setValue(intBuf)
+ ^ (tag.getDataType() == ExifTag.TYPE_LONG));
+ intBuf[count - 1] = 0;
+ }
+
+ private void checkTypeRational(ExifTag tag, Rational rationalBuf[]) {
+ int count = rationalBuf.length;
+ Rational r = rationalBuf[count - 1];
+ rationalBuf[count - 1] = new Rational(MAX_LONG, MIN_LONG);
+
+ assertFalse("\nTag: " + tag.toString(), tag.setValue(rationalBuf)
+ ^ (tag.getDataType() == ExifTag.TYPE_RATIONAL));
+
+ if (tag.getDataType() == ExifTag.TYPE_RATIONAL) {
+ // check overflow
+
+ rationalBuf[count - 1] = new Rational(MAX_LONG + 1L, MIN_LONG);
+ assertFalse(tag.setValue(rationalBuf));
+
+ rationalBuf[count - 1] = new Rational(MAX_LONG, MIN_LONG - 1L);
+ assertFalse(tag.setValue(rationalBuf));
+ }
+ rationalBuf[count - 1] = r;
+ }
+
+ private void checkTypeUnsignedRational(ExifTag tag, Rational rationalBuf[]) {
+ int count = rationalBuf.length;
+ Rational r = rationalBuf[count - 1];
+ rationalBuf[count - 1] = new Rational(MAX_UNSIGNED_LONG, MAX_UNSIGNED_LONG);
+
+ assertFalse("\nTag: " + tag.toString(), tag.setValue(rationalBuf)
+ ^ (tag.getDataType() == ExifTag.TYPE_UNSIGNED_RATIONAL));
+
+ if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_RATIONAL) {
+ // check overflow
+ rationalBuf[count - 1] = new Rational(MAX_UNSIGNED_LONG + 1, 0);
+ assertFalse(tag.setValue(rationalBuf));
+
+ rationalBuf[count - 1] = new Rational(MAX_UNSIGNED_LONG, -1);
+ assertFalse(tag.setValue(rationalBuf));
+ }
+ rationalBuf[count - 1] = r;
+ }
+}
diff --git a/tests/src/com/android/gallery3d/exif/ExifTestRunner.java b/tests/src/com/android/gallery3d/exif/ExifTestRunner.java
new file mode 100644
index 0000000..162baea
--- /dev/null
+++ b/tests/src/com/android/gallery3d/exif/ExifTestRunner.java
@@ -0,0 +1,135 @@
+/*
+ * 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.Context;
+import android.os.Environment;
+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.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ExifTestRunner extends InstrumentationTestRunner {
+ private static final String TAG = "ExifTestRunner";
+
+ private static final int[] IMG_RESOURCE = {
+ R.raw.galaxy_nexus
+ };
+
+ private static final int[] EXIF_DATA_RESOURCE = {
+ R.xml.galaxy_nexus
+ };
+
+ private static List<String> mTestImgPath = new ArrayList<String>();
+ private static List<String> mTestXmlPath = new ArrayList<String>();
+
+ @Override
+ public TestSuite getAllTests() {
+ getTestImagePath();
+ TestSuite suite = new InstrumentationTestSuite(this);
+ suite.addTestSuite(ExifDataTest.class);
+ suite.addTestSuite(ExifTagTest.class);
+ addAllTestsFromExifTestCase(ExifParserTest.class, suite);
+ addAllTestsFromExifTestCase(ExifReaderTest.class, suite);
+ addAllTestsFromExifTestCase(ExifOutputStreamTest.class, suite);
+ addAllTestsFromExifTestCase(ExifModifierTest.class, suite);
+ addAllTestsFromExifTestCase(ExifInterfaceTest.class, suite);
+ return suite;
+ }
+
+ private void getTestImagePath() {
+ Context context = getContext();
+ File imgDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
+ File xmlDir = new File(context.getExternalFilesDir(null).getPath(), "Xml");
+
+ if (imgDir != null && xmlDir != null) {
+ String[] imgs = imgDir.list();
+ if (imgs == null) {
+ return;
+ }
+ for (String imgName : imgs) {
+ String xmlName = imgName.substring(0, imgName.lastIndexOf('.')) + ".xml";
+ File xmlFile = new File(xmlDir, xmlName);
+ if (xmlFile.exists()) {
+ mTestImgPath.add(new File(imgDir, imgName).getAbsolutePath());
+ mTestXmlPath.add(xmlFile.getAbsolutePath());
+ }
+ }
+ }
+ }
+
+ private void addAllTestsFromExifTestCase(Class<? extends ExifXmlDataTestCase> 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;
+ 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);
+ }
+ }
+ for (int i = 0, n = mTestImgPath.size(); i < n; i++) {
+ TestCase test;
+ try {
+ test = testClass.getDeclaredConstructor(String.class, String.class).
+ newInstance(mTestImgPath.get(i), mTestXmlPath.get(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);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public ClassLoader getLoader() {
+ return ExifTestRunner.class.getClassLoader();
+ }
+}
diff --git a/tests/src/com/android/gallery3d/exif/ExifXmlDataTestCase.java b/tests/src/com/android/gallery3d/exif/ExifXmlDataTestCase.java
new file mode 100644
index 0000000..da86020
--- /dev/null
+++ b/tests/src/com/android/gallery3d/exif/ExifXmlDataTestCase.java
@@ -0,0 +1,108 @@
+/*
+ * 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.Resources;
+import android.test.InstrumentationTestCase;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class ExifXmlDataTestCase extends InstrumentationTestCase {
+
+ private static final String RES_ID_TITLE = "Resource ID: %x";
+
+ private InputStream mImageInputStream;
+ private InputStream mXmlInputStream;
+ private XmlPullParser mXmlParser;
+ private final String mImagePath;
+ private final String mXmlPath;
+ private final int mImageResourceId;
+ private final int mXmlResourceId;
+
+ public ExifXmlDataTestCase(int imageRes, int xmlRes) {
+ mImagePath = null;
+ mXmlPath = null;
+ mImageResourceId = imageRes;
+ mXmlResourceId = xmlRes;
+ }
+
+ public ExifXmlDataTestCase(String imagePath, String xmlPath) {
+ mImagePath = imagePath;
+ mXmlPath = xmlPath;
+ mImageResourceId = 0;
+ mXmlResourceId = 0;
+ }
+
+ protected InputStream getImageInputStream() {
+ return mImageInputStream;
+ }
+
+ protected XmlPullParser getXmlParser() {
+ return mXmlParser;
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ try {
+ if (mImagePath != null) {
+ mImageInputStream = new FileInputStream(mImagePath);
+ mXmlInputStream = new FileInputStream(mXmlPath);
+ mXmlParser = Xml.newPullParser();
+ mXmlParser.setInput(new InputStreamReader(mXmlInputStream));
+ } else {
+ Resources res = getInstrumentation().getContext().getResources();
+ mImageInputStream = res.openRawResource(mImageResourceId);
+ mXmlParser = res.getXml(mXmlResourceId);
+ }
+ } catch (Exception e) {
+ throw new Exception(getImageTitle(), e);
+ }
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ Util.closeSilently(mImageInputStream);
+ Util.closeSilently(mXmlInputStream);
+ mXmlParser = null;
+ }
+
+ protected String getImageTitle() {
+ if (mImagePath != null) {
+ return mImagePath;
+ } else {
+ return String.format(RES_ID_TITLE, mImageResourceId);
+ }
+ }
+
+ protected InputStream reopenFileStream() throws Exception {
+ try {
+ if (mImagePath != null) {
+ return new FileInputStream(mImagePath);
+ } else {
+ Resources res = getInstrumentation().getContext().getResources();
+ return res.openRawResource(mImageResourceId);
+ }
+ } catch (Exception e) {
+ throw new Exception(getImageTitle(), e);
+ }
+ }
+}
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..12e9cf7
--- /dev/null
+++ b/tests/src/com/android/gallery3d/exif/ExifXmlReader.java
@@ -0,0 +1,125 @@
+/*
+ * 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.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ExifXmlReader {
+ private static final String TAG_EXIF = "exif";
+ private static final String TAG_TAG = "tag";
+
+ private static final String IFD0 = "IFD0";
+ private static final String EXIF_IFD = "ExifIFD";
+ private static final String GPS_IFD = "GPS";
+ private static final String IFD1 = "IFD1";
+ private static final String INTEROP_IFD = "InteropIFD";
+
+ private static final String ATTR_ID = "id";
+ private static final String ATTR_IFD = "ifd";
+
+ private static final String NO_VALUE = "NO_VALUE";
+
+ /**
+ * This function read the ground truth XML.
+ *
+ * @throws XmlPullParserException
+ * @throws IOException
+ */
+ static public List<Map<Short, List<String>>> readXml(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+
+ List<Map<Short, List<String>>> exifData =
+ new ArrayList<Map<Short, List<String>>>(IfdId.TYPE_IFD_COUNT);
+ for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+ exifData.add(new HashMap<Short, List<String>>());
+ }
+
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ if (parser.getEventType() == XmlPullParser.START_TAG) {
+ break;
+ }
+ }
+ parser.require(XmlPullParser.START_TAG, null, TAG_EXIF);
+
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ parser.require(XmlPullParser.START_TAG, null, TAG_TAG);
+
+ int ifdId = getIfdIdFromString(parser.getAttributeValue(null, ATTR_IFD));
+ short id = Integer.decode(parser.getAttributeValue(null, ATTR_ID)).shortValue();
+
+ String value = "";
+ if (parser.next() == XmlPullParser.TEXT) {
+ value = parser.getText();
+ parser.next();
+ }
+
+ if (ifdId < 0) {
+ // TODO: the MarkerNote segment.
+ } else {
+ List<String> tagData = exifData.get(ifdId).get(id);
+ if (tagData == null) {
+ tagData = new ArrayList<String>();
+ exifData.get(ifdId).put(id, tagData);
+ }
+ if (NO_VALUE.equals(value)) {
+ tagData.add(null);
+ } else {
+ tagData.add(value.trim());
+ }
+ }
+
+ parser.require(XmlPullParser.END_TAG, null, null);
+ }
+ return exifData;
+ }
+
+ static private int getIfdIdFromString(String prefix) {
+ if (IFD0.equals(prefix)) {
+ return IfdId.TYPE_IFD_0;
+ } else if (EXIF_IFD.equals(prefix)) {
+ return IfdId.TYPE_IFD_EXIF;
+ } else if (GPS_IFD.equals(prefix)) {
+ return IfdId.TYPE_IFD_GPS;
+ } else if (IFD1.equals(prefix)) {
+ return IfdId.TYPE_IFD_1;
+ } else if (INTEROP_IFD.equals(prefix)) {
+ return IfdId.TYPE_IFD_INTEROPERABILITY;
+ } else {
+ assert (false);
+ return -1;
+ }
+ }
+
+ static public int getTrueTagNumber(Map<Short, List<String>> ifdData) {
+ int size = 0;
+ for (List<String> tag : ifdData.values()) {
+ size += tag.size();
+ }
+ return size;
+ }
+}
diff --git a/tests/src/com/android/gallery3d/exif/Util.java b/tests/src/com/android/gallery3d/exif/Util.java
new file mode 100644
index 0000000..15de007
--- /dev/null
+++ b/tests/src/com/android/gallery3d/exif/Util.java
@@ -0,0 +1,195 @@
+/*
+ * 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.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+class Util {
+ public static boolean equals(Object a, Object b) {
+ return (a == b) || (a == null ? false : a.equals(b));
+ }
+
+ public static void closeSilently(Closeable c) {
+ if (c == null)
+ return;
+ try {
+ c.close();
+ } catch (Throwable t) {
+ // do nothing
+ }
+ }
+
+ public static byte[] readToByteArray(InputStream is) throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ int len;
+ byte[] buf = new byte[1024];
+ while ((len = is.read(buf)) > -1) {
+ bos.write(buf, 0, len);
+ }
+ bos.flush();
+ return bos.toByteArray();
+ }
+
+ /**
+ * Tags that are not defined in the spec.
+ */
+ static final short TAG_XP_TITLE = (short) 0x9c9b;
+ static final short TAG_XP_COMMENT = (short) 0x9c9c;
+ static final short TAG_XP_AUTHOR = (short) 0x9c9d;
+ static final short TAG_XP_KEYWORDS = (short) 0x9c9e;
+ static final short TAG_XP_SUBJECT = (short) 0x9c9f;
+
+ private static String tagUndefinedTypeValueToString(ExifTag tag) {
+ StringBuilder sbuilder = new StringBuilder();
+ byte[] buf = new byte[tag.getComponentCount()];
+ tag.getBytes(buf);
+ short tagId = tag.getTagId();
+ if (tagId == ExifInterface.getTrueTagKey(ExifInterface.TAG_COMPONENTS_CONFIGURATION)) {
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ if (i != 0) {
+ sbuilder.append(" ");
+ }
+ sbuilder.append(buf[i]);
+ }
+ } else {
+ if (buf.length == 1) {
+ sbuilder.append(buf[0]);
+ } else {
+ for (int i = 0, n = buf.length; i < n; i++) {
+ byte code = buf[i];
+ if (code == 0) {
+ continue;
+ }
+ if (code > 31 && code < 127) {
+ sbuilder.append((char) code);
+ } else {
+ sbuilder.append('.');
+ }
+ }
+ }
+ }
+ return sbuilder.toString();
+ }
+
+ /**
+ * Returns a string representation of the value of this tag.
+ */
+ public static String tagValueToString(ExifTag tag) {
+ StringBuilder sbuilder = new StringBuilder();
+ short id = tag.getTagId();
+ switch (tag.getDataType()) {
+ case ExifTag.TYPE_UNDEFINED:
+ sbuilder.append(tagUndefinedTypeValueToString(tag));
+ break;
+ case ExifTag.TYPE_UNSIGNED_BYTE:
+ if (id == ExifInterface.TAG_MAKER_NOTE || id == TAG_XP_TITLE ||
+ id == TAG_XP_COMMENT || id == TAG_XP_AUTHOR ||
+ id == TAG_XP_KEYWORDS || id == TAG_XP_SUBJECT) {
+ sbuilder.append(tagUndefinedTypeValueToString(tag));
+ } else {
+ byte[] buf = new byte[tag.getComponentCount()];
+ tag.getBytes(buf);
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ if (i != 0)
+ sbuilder.append(" ");
+ sbuilder.append(buf[i]);
+ }
+ }
+ break;
+ case ExifTag.TYPE_ASCII:
+ byte[] buf = tag.getStringByte();
+ for (int i = 0, n = buf.length; i < n; i++) {
+ byte code = buf[i];
+ if (code == 0) {
+ // Treat some tag as undefined type data.
+ if (id == ExifInterface.TAG_COPYRIGHT
+ || id == ExifInterface.TAG_GPS_DATE_STAMP) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ if (code > 31 && code < 127) {
+ sbuilder.append((char) code);
+ } else {
+ sbuilder.append('.');
+ }
+ }
+ break;
+ case ExifTag.TYPE_UNSIGNED_LONG:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ if (i != 0) {
+ sbuilder.append(" ");
+ }
+ sbuilder.append(tag.getValueAt(i));
+ }
+ break;
+ case ExifTag.TYPE_RATIONAL:
+ case ExifTag.TYPE_UNSIGNED_RATIONAL:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ Rational r = tag.getRational(i);
+ if (i != 0) {
+ sbuilder.append(" ");
+ }
+ sbuilder.append(r.getNumerator()).append("/").append(r.getDenominator());
+ }
+ break;
+ case ExifTag.TYPE_UNSIGNED_SHORT:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ if (i != 0) {
+ sbuilder.append(" ");
+ }
+ sbuilder.append((int) tag.getValueAt(i));
+ }
+ break;
+ case ExifTag.TYPE_LONG:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ if (i != 0) {
+ sbuilder.append(" ");
+ }
+ sbuilder.append((int) tag.getValueAt(i));
+ }
+ break;
+ }
+ return sbuilder.toString();
+ }
+
+ public static String valueToString(Object obj) {
+ if (obj instanceof int[]) {
+ return Arrays.toString((int[]) obj);
+ } else if (obj instanceof Integer[]) {
+ return Arrays.toString((Integer[]) obj);
+ } else if (obj instanceof long[]) {
+ return Arrays.toString((long[]) obj);
+ } else if (obj instanceof Long[]) {
+ return Arrays.toString((Long[]) obj);
+ } else if (obj instanceof Rational) {
+ return ((Rational) obj).toString();
+ } else if (obj instanceof Rational[]) {
+ return Arrays.toString((Rational[]) obj);
+ } else if (obj instanceof byte[]) {
+ return Arrays.toString((byte[]) obj);
+ } else if (obj != null) {
+ return obj.toString();
+ }
+ return "";
+ }
+}
diff --git a/tests/src/com/android/gallery3d/functional/CameraTest.java b/tests/src/com/android/gallery3d/functional/CameraTest.java
new file mode 100644
index 0000000..c293c0d
--- /dev/null
+++ b/tests/src/com/android/gallery3d/functional/CameraTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2010 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.functional;
+
+import com.android.camera.CameraActivity;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.Process;
+import android.provider.MediaStore;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import java.io.File;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+public class CameraTest extends InstrumentationTestCase {
+ @LargeTest
+ public void testVideoCaptureIntentFdLeak() throws Exception {
+ Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
+ intent.setClass(getInstrumentation().getTargetContext(), CameraActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.parse("file://"
+ + Environment.getExternalStorageDirectory().toString()
+ + "test_fd_leak.3gp"));
+ getInstrumentation().startActivitySync(intent).finish();
+ // Test if the fd is closed.
+ for (File f: new File("/proc/" + Process.myPid() + "/fd").listFiles()) {
+ assertEquals(-1, f.getCanonicalPath().indexOf("test_fd_leak.3gp"));
+ }
+ }
+
+ @LargeTest
+ public void testActivityLeak() throws Exception {
+ checkActivityLeak(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
+ checkActivityLeak(MediaStore.INTENT_ACTION_VIDEO_CAMERA);
+ }
+
+ private void checkActivityLeak(String action) throws Exception {
+ final int TEST_COUNT = 5;
+ Intent intent = new Intent(action);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setClass(getInstrumentation().getTargetContext(),
+ CameraActivity.class);
+ ArrayList<WeakReference<Activity>> refs =
+ new ArrayList<WeakReference<Activity>>();
+ for (int i = 0; i < TEST_COUNT; i++) {
+ Activity activity = getInstrumentation().startActivitySync(intent);
+ refs.add(new WeakReference<Activity>(activity));
+ activity.finish();
+ getInstrumentation().waitForIdleSync();
+ activity = null;
+ }
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().runFinalization();
+ Runtime.getRuntime().gc();
+ int refCount = 0;
+ for (WeakReference<Activity> c: refs) {
+ if (c.get() != null) refCount++;
+ }
+ // If applications are leaking activity, every reference is reachable.
+ assertTrue(refCount != TEST_COUNT);
+ }
+}
diff --git a/tests/src/com/android/gallery3d/functional/ImageCaptureIntentTest.java b/tests/src/com/android/gallery3d/functional/ImageCaptureIntentTest.java
new file mode 100644
index 0000000..8d394b5
--- /dev/null
+++ b/tests/src/com/android/gallery3d/functional/ImageCaptureIntentTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2011 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.functional;
+
+import com.android.camera.CameraActivity;
+import com.android.gallery3d.R;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.KeyEvent;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+
+public class ImageCaptureIntentTest extends ActivityInstrumentationTestCase2 <CameraActivity> {
+ private Intent mIntent;
+
+ public ImageCaptureIntentTest() {
+ super(CameraActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+ }
+
+ @LargeTest
+ public void testNoExtraOutput() throws Exception {
+ setActivityIntent(mIntent);
+ getActivity();
+
+ takePicture();
+ pressDone();
+
+ assertTrue(getActivity().isFinishing());
+ assertEquals(Activity.RESULT_OK, getActivity().getResultCode());
+ Intent resultData = getActivity().getResultData();
+ Bitmap bitmap = (Bitmap) resultData.getParcelableExtra("data");
+ assertNotNull(bitmap);
+ assertTrue(bitmap.getWidth() > 0);
+ assertTrue(bitmap.getHeight() > 0);
+ }
+
+ @LargeTest
+ public void testExtraOutput() throws Exception {
+ File file = new File(Environment.getExternalStorageDirectory(),
+ "test.jpg");
+ BufferedInputStream stream = null;
+ byte[] jpegData;
+
+ try {
+ mIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
+ setActivityIntent(mIntent);
+ getActivity();
+
+ takePicture();
+ pressDone();
+
+ assertTrue(getActivity().isFinishing());
+ assertEquals(Activity.RESULT_OK, getActivity().getResultCode());
+
+ // Verify the jpeg file
+ int fileLength = (int) file.length();
+ assertTrue(fileLength > 0);
+ jpegData = new byte[fileLength];
+ stream = new BufferedInputStream(new FileInputStream(file));
+ stream.read(jpegData);
+ } finally {
+ if (stream != null) stream.close();
+ file.delete();
+ }
+
+ Bitmap b = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
+ assertTrue(b.getWidth() > 0);
+ assertTrue(b.getHeight() > 0);
+ }
+
+ @LargeTest
+ public void testCancel() throws Exception {
+ setActivityIntent(mIntent);
+ getActivity();
+
+ pressCancel();
+
+ assertTrue(getActivity().isFinishing());
+ assertEquals(Activity.RESULT_CANCELED, getActivity().getResultCode());
+ }
+
+ @LargeTest
+ public void testSnapshotCancel() throws Exception {
+ setActivityIntent(mIntent);
+ getActivity();
+
+ takePicture();
+ pressCancel();
+
+ assertTrue(getActivity().isFinishing());
+ assertEquals(Activity.RESULT_CANCELED, getActivity().getResultCode());
+ }
+
+ private void takePicture() throws Exception {
+ getInstrumentation().sendKeySync(
+ new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_FOCUS));
+ getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_CAMERA);
+ Thread.sleep(4000);
+ }
+
+ private void pressDone() {
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ getActivity().findViewById(R.id.btn_done).performClick();
+ }
+ });
+ }
+
+ private void pressCancel() {
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ getActivity().findViewById(R.id.btn_cancel).performClick();
+ }
+ });
+ }
+}
diff --git a/tests/src/com/android/gallery3d/functional/VideoCaptureIntentTest.java b/tests/src/com/android/gallery3d/functional/VideoCaptureIntentTest.java
new file mode 100644
index 0000000..c8d7bbb
--- /dev/null
+++ b/tests/src/com/android/gallery3d/functional/VideoCaptureIntentTest.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2011 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.functional;
+
+import com.android.camera.CameraActivity;
+import com.android.gallery3d.R;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.database.Cursor;
+import android.media.MediaMetadataRetriever;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Video.VideoColumns;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import java.io.File;
+
+public class VideoCaptureIntentTest extends ActivityInstrumentationTestCase2 <CameraActivity> {
+ private static final String TAG = "VideoCaptureIntentTest";
+ private Intent mIntent;
+ private Uri mVideoUri;
+ private File mFile, mFile2;
+
+ public VideoCaptureIntentTest() {
+ super(CameraActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (mVideoUri != null) {
+ ContentResolver resolver = getActivity().getContentResolver();
+ Uri query = mVideoUri.buildUpon().build();
+ String[] projection = new String[] {VideoColumns.DATA};
+
+ Cursor cursor = null;
+ try {
+ cursor = resolver.query(query, projection, null, null, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ new File(cursor.getString(0)).delete();
+ }
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+
+ resolver.delete(mVideoUri, null, null);
+ }
+ if (mFile != null) mFile.delete();
+ if (mFile2 != null) mFile2.delete();
+ super.tearDown();
+ }
+
+ @LargeTest
+ public void testNoExtraOutput() throws Exception {
+ setActivityIntent(mIntent);
+ getActivity();
+
+ recordVideo();
+ pressDone();
+
+ Intent resultData = getActivity().getResultData();
+ mVideoUri = resultData.getData();
+ assertNotNull(mVideoUri);
+ verify(getActivity(), mVideoUri);
+ }
+
+ @LargeTest
+ public void testExtraOutput() throws Exception {
+ mFile = new File(Environment.getExternalStorageDirectory(), "video.tmp");
+
+ Uri uri = Uri.fromFile(mFile);
+ mIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
+ setActivityIntent(mIntent);
+ getActivity();
+
+ recordVideo();
+ pressDone();
+
+ verify(getActivity(), uri);
+ }
+
+ @LargeTest
+ public void testCancel() throws Exception {
+ setActivityIntent(mIntent);
+ getActivity();
+
+ pressCancel();
+
+ assertTrue(getActivity().isFinishing());
+ assertEquals(Activity.RESULT_CANCELED, getActivity().getResultCode());
+ }
+
+ @LargeTest
+ public void testRecordCancel() throws Exception {
+ setActivityIntent(mIntent);
+ getActivity();
+
+ recordVideo();
+ pressCancel();
+
+ assertTrue(getActivity().isFinishing());
+ assertEquals(Activity.RESULT_CANCELED, getActivity().getResultCode());
+ }
+
+ @LargeTest
+ public void testExtraSizeLimit() throws Exception {
+ mFile = new File(Environment.getExternalStorageDirectory(), "video.tmp");
+ final long sizeLimit = 500000; // bytes
+
+ Uri uri = Uri.fromFile(mFile);
+ mIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
+ mIntent.putExtra(MediaStore.EXTRA_SIZE_LIMIT, sizeLimit);
+ mIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0); // use low quality to speed up
+ setActivityIntent(mIntent);
+ getActivity();
+
+ recordVideo(5000);
+ pressDone();
+
+ verify(getActivity(), uri);
+ long length = mFile.length();
+ Log.v(TAG, "Video size is " + length + " bytes.");
+ assertTrue(length > 0);
+ assertTrue("Actual size=" + length, length <= sizeLimit);
+ }
+
+ @LargeTest
+ public void testExtraDurationLimit() throws Exception {
+ mFile = new File(Environment.getExternalStorageDirectory(), "video.tmp");
+ final int durationLimit = 2; // seconds
+
+ Uri uri = Uri.fromFile(mFile);
+ mIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
+ mIntent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, durationLimit);
+ setActivityIntent(mIntent);
+ getActivity();
+
+ recordVideo(5000);
+ pressDone();
+
+ int duration = verify(getActivity(), uri);
+ // The duraion should be close to to the limit. The last video duration
+ // also has duration, so the total duration may exceeds the limit a
+ // little bit.
+ Log.v(TAG, "Video length is " + duration + " ms.");
+ assertTrue(duration < (durationLimit + 1) * 1000);
+ }
+
+ @LargeTest
+ public void testExtraVideoQuality() throws Exception {
+ mFile = new File(Environment.getExternalStorageDirectory(), "video.tmp");
+ mFile2 = new File(Environment.getExternalStorageDirectory(), "video2.tmp");
+
+ Uri uri = Uri.fromFile(mFile);
+ mIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
+ mIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0); // low quality
+ setActivityIntent(mIntent);
+ getActivity();
+
+ recordVideo();
+ pressDone();
+
+ verify(getActivity(), uri);
+ setActivity(null);
+
+ uri = Uri.fromFile(mFile2);
+ mIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
+ mIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1); // high quality
+ setActivityIntent(mIntent);
+ getActivity();
+
+ recordVideo();
+ pressDone();
+
+ verify(getActivity(), uri);
+ assertTrue(mFile.length() <= mFile2.length());
+ }
+
+ // Verify result code, result data, and the duration.
+ private int verify(CameraActivity activity, Uri uri) throws Exception {
+ assertTrue(activity.isFinishing());
+ assertEquals(Activity.RESULT_OK, activity.getResultCode());
+
+ // Verify the video file
+ MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+ retriever.setDataSource(activity, uri);
+ String duration = retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_DURATION);
+ assertNotNull(duration);
+ int durationValue = Integer.parseInt(duration);
+ Log.v(TAG, "Video duration is " + durationValue);
+ assertTrue(durationValue > 0);
+ return durationValue;
+ }
+
+ private void recordVideo(int ms) throws Exception {
+ getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_CAMERA);
+ Thread.sleep(ms);
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ // If recording is in progress, stop it. Run these atomically in
+ // UI thread.
+ CameraActivity activity = getActivity();
+ if (activity.isRecording()) {
+ activity.findViewById(R.id.shutter_button).performClick();
+ }
+ }
+ });
+ }
+
+ private void recordVideo() throws Exception {
+ recordVideo(2000);
+ }
+
+ private void pressDone() {
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ getActivity().findViewById(R.id.btn_done).performClick();
+ }
+ });
+ }
+
+ private void pressCancel() {
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ getActivity().findViewById(R.id.btn_cancel).performClick();
+ }
+ });
+ }
+}
diff --git a/tests/src/com/android/gallery3d/glrenderer/GLCanvasMock.java b/tests/src/com/android/gallery3d/glrenderer/GLCanvasMock.java
new file mode 100644
index 0000000..a57c188
--- /dev/null
+++ b/tests/src/com/android/gallery3d/glrenderer/GLCanvasMock.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 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.glrenderer;
+
+import com.android.gallery3d.glrenderer.BasicTexture;
+import com.android.gallery3d.ui.GLCanvasStub;
+
+import javax.microedition.khronos.opengles.GL11;
+
+public class GLCanvasMock extends GLCanvasStub {
+ // fillRect
+ int mFillRectCalled;
+ float mFillRectWidth;
+ float mFillRectHeight;
+ int mFillRectColor;
+ // drawMixed
+ int mDrawMixedCalled;
+ float mDrawMixedRatio;
+ // drawTexture;
+ int mDrawTextureCalled;
+
+ private GL11 mGL;
+
+ public GLCanvasMock(GL11 gl) {
+ mGL = gl;
+ }
+
+ public GLCanvasMock() {
+ mGL = new GLStub();
+ }
+
+ @Override
+ public GL11 getGLInstance() {
+ return mGL;
+ }
+
+ @Override
+ public void fillRect(float x, float y, float width, float height, int color) {
+ mFillRectCalled++;
+ mFillRectWidth = width;
+ mFillRectHeight = height;
+ mFillRectColor = color;
+ }
+
+ @Override
+ public void drawTexture(
+ BasicTexture texture, int x, int y, int width, int height) {
+ mDrawTextureCalled++;
+ }
+
+ @Override
+ public void drawMixed(BasicTexture from, BasicTexture to,
+ float ratio, int x, int y, int w, int h) {
+ mDrawMixedCalled++;
+ mDrawMixedRatio = ratio;
+ }
+}
diff --git a/tests/src/com/android/gallery3d/glrenderer/GLCanvasTest.java b/tests/src/com/android/gallery3d/glrenderer/GLCanvasTest.java
new file mode 100644
index 0000000..416c114
--- /dev/null
+++ b/tests/src/com/android/gallery3d/glrenderer/GLCanvasTest.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2010 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.glrenderer;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+
+import javax.microedition.khronos.opengles.GL10;
+import javax.microedition.khronos.opengles.GL11;
+
+public class GLCanvasTest extends TestCase {
+ private static final String TAG = "GLCanvasTest";
+
+ private static GLPaint newColorPaint(int color) {
+ GLPaint paint = new GLPaint();
+ paint.setColor(color);
+ return paint;
+ }
+
+ @SmallTest
+ public void testSetSize() {
+ GL11 glStub = new GLStub();
+ GLCanvas canvas = new GLES11Canvas(glStub);
+ canvas.setSize(100, 200);
+ canvas.setSize(1000, 100);
+ try {
+ canvas.setSize(-1, 100);
+ fail();
+ } catch (Throwable ex) {
+ // expected.
+ }
+ }
+
+ @SmallTest
+ public void testClearBuffer() {
+ new ClearBufferTest().run();
+ }
+
+ private static class ClearBufferTest extends GLMock {
+ void run() {
+ GLCanvas canvas = new GLES11Canvas(this);
+ assertEquals(0, mGLClearCalled);
+ canvas.clearBuffer();
+ assertEquals(GL10.GL_COLOR_BUFFER_BIT, mGLClearMask);
+ assertEquals(1, mGLClearCalled);
+ }
+ }
+
+ @SmallTest
+ public void testSetColor() {
+ new SetColorTest().run();
+ }
+
+ // This test assumes we use pre-multipled alpha blending and should
+ // set the blending function and color correctly.
+ private static class SetColorTest extends GLMock {
+ void run() {
+ int[] testColors = new int[] {
+ 0, 0xFFFFFFFF, 0xFF000000, 0x00FFFFFF, 0x80FF8001,
+ 0x7F010101, 0xFEFEFDFC, 0x017F8081, 0x027F8081, 0x2ADE4C4D
+ };
+
+ GLCanvas canvas = new GLES11Canvas(this);
+ canvas.setSize(400, 300);
+ // Test one color to make sure blend function is set.
+ assertEquals(0, mGLColorCalled);
+ canvas.drawLine(0, 0, 1, 1, newColorPaint(0x7F804020));
+ assertEquals(1, mGLColorCalled);
+ assertEquals(0x7F402010, mGLColor);
+ assertPremultipliedBlending(this);
+
+ // Test other colors to make sure premultiplication is right
+ for (int c : testColors) {
+ float a = (c >>> 24) / 255f;
+ float r = ((c >> 16) & 0xff) / 255f;
+ float g = ((c >> 8) & 0xff) / 255f;
+ float b = (c & 0xff) / 255f;
+ int pre = makeColor4f(a * r, a * g, a * b, a);
+
+ mGLColorCalled = 0;
+ canvas.drawLine(0, 0, 1, 1, newColorPaint(c));
+ assertEquals(1, mGLColorCalled);
+ assertEquals(pre, mGLColor);
+ }
+ }
+ }
+
+ @SmallTest
+ public void testSetGetMultiplyAlpha() {
+ GL11 glStub = new GLStub();
+ GLCanvas canvas = new GLES11Canvas(glStub);
+
+ canvas.setAlpha(1f);
+ assertEquals(1f, canvas.getAlpha());
+
+ canvas.setAlpha(0f);
+ assertEquals(0f, canvas.getAlpha());
+
+ canvas.setAlpha(0.5f);
+ assertEquals(0.5f, canvas.getAlpha());
+
+ canvas.multiplyAlpha(0.5f);
+ assertEquals(0.25f, canvas.getAlpha());
+
+ canvas.multiplyAlpha(0f);
+ assertEquals(0f, canvas.getAlpha());
+
+ try {
+ canvas.setAlpha(-0.01f);
+ fail();
+ } catch (Throwable ex) {
+ // expected.
+ }
+
+ try {
+ canvas.setAlpha(1.01f);
+ fail();
+ } catch (Throwable ex) {
+ // expected.
+ }
+ }
+
+ @SmallTest
+ public void testAlpha() {
+ new AlphaTest().run();
+ }
+
+ private static class AlphaTest extends GLMock {
+ void run() {
+ GLCanvas canvas = new GLES11Canvas(this);
+ canvas.setSize(400, 300);
+
+ assertEquals(0, mGLColorCalled);
+ canvas.setAlpha(0.48f);
+ canvas.drawLine(0, 0, 1, 1, newColorPaint(0xFF804020));
+ assertPremultipliedBlending(this);
+ assertEquals(1, mGLColorCalled);
+ assertEquals(0x7A3D1F0F, mGLColor);
+ }
+ }
+
+ @SmallTest
+ public void testDrawLine() {
+ new DrawLineTest().run();
+ }
+
+ // This test assumes the drawLine() function use glDrawArrays() with
+ // GL_LINE_STRIP mode to draw the line and the input coordinates are used
+ // directly.
+ private static class DrawLineTest extends GLMock {
+ private int mDrawArrayCalled = 0;
+ private final int[] mResult = new int[4];
+
+ @Override
+ public void glDrawArrays(int mode, int first, int count) {
+ assertNotNull(mGLVertexPointer);
+ assertEquals(GL10.GL_LINE_STRIP, mode);
+ assertEquals(2, count);
+ mGLVertexPointer.bindByteBuffer();
+
+ double[] coord = new double[4];
+ mGLVertexPointer.getArrayElement(first, coord);
+ mResult[0] = (int) coord[0];
+ mResult[1] = (int) coord[1];
+ mGLVertexPointer.getArrayElement(first + 1, coord);
+ mResult[2] = (int) coord[0];
+ mResult[3] = (int) coord[1];
+ mDrawArrayCalled++;
+ }
+
+ void run() {
+ GLCanvas canvas = new GLES11Canvas(this);
+ canvas.setSize(400, 300);
+ canvas.drawLine(2, 7, 1, 8, newColorPaint(0) /* color */);
+ assertTrue(mGLVertexArrayEnabled);
+ assertEquals(1, mDrawArrayCalled);
+
+ Log.v(TAG, "result = " + Arrays.toString(mResult));
+ int[] answer = new int[] {2, 7, 1, 8};
+ for (int i = 0; i < answer.length; i++) {
+ assertEquals(answer[i], mResult[i]);
+ }
+ }
+ }
+
+ @SmallTest
+ public void testFillRect() {
+ new FillRectTest().run();
+ }
+
+ // This test assumes the drawLine() function use glDrawArrays() with
+ // GL_TRIANGLE_STRIP mode to draw the line and the input coordinates
+ // are used directly.
+ private static class FillRectTest extends GLMock {
+ private int mDrawArrayCalled = 0;
+ private final int[] mResult = new int[8];
+
+ @Override
+ public void glDrawArrays(int mode, int first, int count) {
+ assertNotNull(mGLVertexPointer);
+ assertEquals(GL10.GL_TRIANGLE_STRIP, mode);
+ assertEquals(4, count);
+ mGLVertexPointer.bindByteBuffer();
+
+ double[] coord = new double[4];
+ for (int i = 0; i < 4; i++) {
+ mGLVertexPointer.getArrayElement(first + i, coord);
+ mResult[i * 2 + 0] = (int) coord[0];
+ mResult[i * 2 + 1] = (int) coord[1];
+ }
+
+ mDrawArrayCalled++;
+ }
+
+ void run() {
+ GLCanvas canvas = new GLES11Canvas(this);
+ canvas.setSize(400, 300);
+ canvas.fillRect(2, 7, 1, 8, 0 /* color */);
+ assertTrue(mGLVertexArrayEnabled);
+ assertEquals(1, mDrawArrayCalled);
+ Log.v(TAG, "result = " + Arrays.toString(mResult));
+
+ // These are the four vertics that should be used.
+ int[] answer = new int[] {
+ 2, 7,
+ 3, 7,
+ 3, 15,
+ 2, 15};
+ int count[] = new int[4];
+
+ // Count the number of appearances for each vertex.
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ if (answer[i * 2] == mResult[j * 2] &&
+ answer[i * 2 + 1] == mResult[j * 2 + 1]) {
+ count[i]++;
+ }
+ }
+ }
+
+ // Each vertex should appear exactly once.
+ for (int i = 0; i < 4; i++) {
+ assertEquals(1, count[i]);
+ }
+ }
+ }
+
+ @SmallTest
+ public void testTransform() {
+ new TransformTest().run();
+ }
+
+ // This test assumes glLoadMatrixf is used to load the model view matrix,
+ // and glOrthof is used to load the projection matrix.
+ //
+ // The projection matrix is set to an orthogonal projection which is the
+ // inverse of viewport transform. So the model view matrix maps input
+ // directly to screen coordinates (default no scaling, and the y-axis is
+ // reversed).
+ //
+ // The matrix here are all listed in column major order.
+ //
+ private static class TransformTest extends GLMock {
+ private final float[] mModelViewMatrixUsed = new float[16];
+ private final float[] mProjectionMatrixUsed = new float[16];
+
+ @Override
+ public void glDrawArrays(int mode, int first, int count) {
+ copy(mModelViewMatrixUsed, mGLModelViewMatrix);
+ copy(mProjectionMatrixUsed, mGLProjectionMatrix);
+ }
+
+ private void copy(float[] dest, float[] src) {
+ System.arraycopy(src, 0, dest, 0, 16);
+ }
+
+ void run() {
+ GLCanvas canvas = new GLES11Canvas(this);
+ canvas.setSize(40, 50);
+ int color = 0;
+
+ // Initial matrix
+ canvas.drawLine(0, 0, 1, 1, newColorPaint(color));
+ assertMatrixEq(new float[] {
+ 1, 0, 0, 0,
+ 0, -1, 0, 0,
+ 0, 0, 1, 0,
+ 0, 50, 0, 1
+ }, mModelViewMatrixUsed);
+
+ assertMatrixEq(new float[] {
+ 2f / 40, 0, 0, 0,
+ 0, 2f / 50, 0, 0,
+ 0, 0, -1, 0,
+ -1, -1, 0, 1
+ }, mProjectionMatrixUsed);
+
+ // Translation
+ canvas.translate(3, 4, 5);
+ canvas.drawLine(0, 0, 1, 1, newColorPaint(color));
+ assertMatrixEq(new float[] {
+ 1, 0, 0, 0,
+ 0, -1, 0, 0,
+ 0, 0, 1, 0,
+ 3, 46, 5, 1
+ }, mModelViewMatrixUsed);
+ canvas.save();
+
+ // Scaling
+ canvas.scale(0.7f, 0.6f, 0.5f);
+ canvas.drawLine(0, 0, 1, 1, newColorPaint(color));
+ assertMatrixEq(new float[] {
+ 0.7f, 0, 0, 0,
+ 0, -0.6f, 0, 0,
+ 0, 0, 0.5f, 0,
+ 3, 46, 5, 1
+ }, mModelViewMatrixUsed);
+
+ // Rotation
+ canvas.rotate(90, 0, 0, 1);
+ canvas.drawLine(0, 0, 1, 1, newColorPaint(color));
+ assertMatrixEq(new float[] {
+ 0, -0.6f, 0, 0,
+ -0.7f, 0, 0, 0,
+ 0, 0, 0.5f, 0,
+ 3, 46, 5, 1
+ }, mModelViewMatrixUsed);
+ canvas.restore();
+
+ // After restoring to the point just after translation,
+ // do rotation again.
+ canvas.rotate(180, 1, 0, 0);
+ canvas.drawLine(0, 0, 1, 1, newColorPaint(color));
+ assertMatrixEq(new float[] {
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, -1, 0,
+ 3, 46, 5, 1
+ }, mModelViewMatrixUsed);
+ }
+ }
+
+ private static void assertPremultipliedBlending(GLMock mock) {
+ assertTrue(mock.mGLBlendFuncCalled > 0);
+ assertTrue(mock.mGLBlendEnabled);
+ assertEquals(GL11.GL_ONE, mock.mGLBlendFuncSFactor);
+ assertEquals(GL11.GL_ONE_MINUS_SRC_ALPHA, mock.mGLBlendFuncDFactor);
+ }
+
+ private static void assertMatrixEq(float[] expected, float[] actual) {
+ try {
+ for (int i = 0; i < 16; i++) {
+ assertFloatEq(expected[i], actual[i]);
+ }
+ } catch (Throwable t) {
+ Log.v(TAG, "expected = " + Arrays.toString(expected) +
+ ", actual = " + Arrays.toString(actual));
+ fail();
+ }
+ }
+
+ public static void assertFloatEq(float expected, float actual) {
+ if (Math.abs(actual - expected) > 1e-6) {
+ Log.v(TAG, "expected: " + expected + ", actual: " + actual);
+ fail();
+ }
+ }
+}
diff --git a/tests/src/com/android/gallery3d/glrenderer/GLMock.java b/tests/src/com/android/gallery3d/glrenderer/GLMock.java
new file mode 100644
index 0000000..b242217
--- /dev/null
+++ b/tests/src/com/android/gallery3d/glrenderer/GLMock.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2010 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.glrenderer;
+
+import com.android.gallery3d.ui.PointerInfo;
+
+import java.nio.Buffer;
+import java.util.HashMap;
+import javax.microedition.khronos.opengles.GL10;
+import javax.microedition.khronos.opengles.GL11;
+
+public class GLMock extends GLStub {
+ @SuppressWarnings("unused")
+ private static final String TAG = "GLMock";
+
+ // glClear
+ int mGLClearCalled;
+ int mGLClearMask;
+ // glBlendFunc
+ int mGLBlendFuncCalled;
+ int mGLBlendFuncSFactor;
+ int mGLBlendFuncDFactor;
+ // glColor4[fx]
+ int mGLColorCalled;
+ int mGLColor;
+ // glEnable, glDisable
+ boolean mGLBlendEnabled;
+ boolean mGLStencilEnabled;
+ // glEnableClientState
+ boolean mGLVertexArrayEnabled;
+ // glVertexPointer
+ PointerInfo mGLVertexPointer;
+ // glMatrixMode
+ int mGLMatrixMode = GL10.GL_MODELVIEW;
+ // glLoadMatrixf
+ float[] mGLModelViewMatrix = new float[16];
+ float[] mGLProjectionMatrix = new float[16];
+ // glBindTexture
+ int mGLBindTextureId;
+ // glTexEnvf
+ HashMap<Integer, Float> mGLTexEnv0 = new HashMap<Integer, Float>();
+ HashMap<Integer, Float> mGLTexEnv1 = new HashMap<Integer, Float>();
+ // glActiveTexture
+ int mGLActiveTexture = GL11.GL_TEXTURE0;
+
+ @Override
+ public void glClear(int mask) {
+ mGLClearCalled++;
+ mGLClearMask = mask;
+ }
+
+ @Override
+ public void glBlendFunc(int sfactor, int dfactor) {
+ mGLBlendFuncSFactor = sfactor;
+ mGLBlendFuncDFactor = dfactor;
+ mGLBlendFuncCalled++;
+ }
+
+ @Override
+ public void glColor4f(float red, float green, float blue,
+ float alpha) {
+ mGLColorCalled++;
+ mGLColor = makeColor4f(red, green, blue, alpha);
+ }
+
+ @Override
+ public void glColor4x(int red, int green, int blue, int alpha) {
+ mGLColorCalled++;
+ mGLColor = makeColor4x(red, green, blue, alpha);
+ }
+
+ @Override
+ public void glEnable(int cap) {
+ if (cap == GL11.GL_BLEND) {
+ mGLBlendEnabled = true;
+ } else if (cap == GL11.GL_STENCIL_TEST) {
+ mGLStencilEnabled = true;
+ }
+ }
+
+ @Override
+ public void glDisable(int cap) {
+ if (cap == GL11.GL_BLEND) {
+ mGLBlendEnabled = false;
+ } else if (cap == GL11.GL_STENCIL_TEST) {
+ mGLStencilEnabled = false;
+ }
+ }
+
+ @Override
+ public void glEnableClientState(int array) {
+ if (array == GL10.GL_VERTEX_ARRAY) {
+ mGLVertexArrayEnabled = true;
+ }
+ }
+
+ @Override
+ public void glVertexPointer(int size, int type, int stride, Buffer pointer) {
+ mGLVertexPointer = new PointerInfo(size, type, stride, pointer);
+ }
+
+ @Override
+ public void glMatrixMode(int mode) {
+ mGLMatrixMode = mode;
+ }
+
+ @Override
+ public void glLoadMatrixf(float[] m, int offset) {
+ if (mGLMatrixMode == GL10.GL_MODELVIEW) {
+ System.arraycopy(m, offset, mGLModelViewMatrix, 0, 16);
+ } else if (mGLMatrixMode == GL10.GL_PROJECTION) {
+ System.arraycopy(m, offset, mGLProjectionMatrix, 0, 16);
+ }
+ }
+
+ @Override
+ public void glOrthof(
+ float left, float right, float bottom, float top,
+ float zNear, float zFar) {
+ float tx = -(right + left) / (right - left);
+ float ty = -(top + bottom) / (top - bottom);
+ float tz = - (zFar + zNear) / (zFar - zNear);
+ float[] m = new float[] {
+ 2 / (right - left), 0, 0, 0,
+ 0, 2 / (top - bottom), 0, 0,
+ 0, 0, -2 / (zFar - zNear), 0,
+ tx, ty, tz, 1
+ };
+ glLoadMatrixf(m, 0);
+ }
+
+ @Override
+ public void glBindTexture(int target, int texture) {
+ if (target == GL11.GL_TEXTURE_2D) {
+ mGLBindTextureId = texture;
+ }
+ }
+
+ @Override
+ public void glTexEnvf(int target, int pname, float param) {
+ if (target == GL11.GL_TEXTURE_ENV) {
+ if (mGLActiveTexture == GL11.GL_TEXTURE0) {
+ mGLTexEnv0.put(pname, param);
+ } else if (mGLActiveTexture == GL11.GL_TEXTURE1) {
+ mGLTexEnv1.put(pname, param);
+ } else {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ public int getTexEnvi(int pname) {
+ return getTexEnvi(mGLActiveTexture, pname);
+ }
+
+ public int getTexEnvi(int activeTexture, int pname) {
+ if (activeTexture == GL11.GL_TEXTURE0) {
+ return (int) mGLTexEnv0.get(pname).floatValue();
+ } else if (activeTexture == GL11.GL_TEXTURE1) {
+ return (int) mGLTexEnv1.get(pname).floatValue();
+ } else {
+ throw new AssertionError();
+ }
+ }
+
+ @Override
+ public void glActiveTexture(int texture) {
+ mGLActiveTexture = texture;
+ }
+
+ public static int makeColor4f(float red, float green, float blue,
+ float alpha) {
+ return (Math.round(alpha * 255) << 24) |
+ (Math.round(red * 255) << 16) |
+ (Math.round(green * 255) << 8) |
+ Math.round(blue * 255);
+ }
+
+ public static int makeColor4x(int red, int green, int blue, int alpha) {
+ final float X = 65536f;
+ return makeColor4f(red / X, green / X, blue / X, alpha / X);
+ }
+}
diff --git a/tests/src/com/android/gallery3d/glrenderer/GLStub.java b/tests/src/com/android/gallery3d/glrenderer/GLStub.java
new file mode 100644
index 0000000..4b66040
--- /dev/null
+++ b/tests/src/com/android/gallery3d/glrenderer/GLStub.java
@@ -0,0 +1,1490 @@
+/*
+ * Copyright (C) 2010 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.glrenderer;
+
+import javax.microedition.khronos.opengles.GL;
+import javax.microedition.khronos.opengles.GL10;
+import javax.microedition.khronos.opengles.GL10Ext;
+import javax.microedition.khronos.opengles.GL11;
+import javax.microedition.khronos.opengles.GL11Ext;
+
+public class GLStub implements GL, GL10, GL10Ext, GL11, GL11Ext {
+ @SuppressWarnings("unused")
+ private static final String TAG = "GLStub";
+
+ public void glActiveTexture(
+ int texture
+ ){}
+
+ public void glAlphaFunc(
+ int func,
+ float ref
+ ){}
+
+ public void glAlphaFuncx(
+ int func,
+ int ref
+ ){}
+
+ public void glBindTexture(
+ int target,
+ int texture
+ ){}
+
+ public void glBlendFunc(
+ int sfactor,
+ int dfactor
+ ){}
+
+ public void glClear(
+ int mask
+ ){}
+
+ public void glClearColor(
+ float red,
+ float green,
+ float blue,
+ float alpha
+ ){}
+
+ public void glClearColorx(
+ int red,
+ int green,
+ int blue,
+ int alpha
+ ){}
+
+ public void glClearDepthf(
+ float depth
+ ){}
+
+ public void glClearDepthx(
+ int depth
+ ){}
+
+ public void glClearStencil(
+ int s
+ ){}
+
+ public void glClientActiveTexture(
+ int texture
+ ){}
+
+ public void glColor4f(
+ float red,
+ float green,
+ float blue,
+ float alpha
+ ){}
+
+ public void glColor4x(
+ int red,
+ int green,
+ int blue,
+ int alpha
+ ){}
+
+ public void glColorMask(
+ boolean red,
+ boolean green,
+ boolean blue,
+ boolean alpha
+ ){}
+
+ public void glColorPointer(
+ int size,
+ int type,
+ int stride,
+ java.nio.Buffer pointer
+ ){}
+
+ public void glCompressedTexImage2D(
+ int target,
+ int level,
+ int internalformat,
+ int width,
+ int height,
+ int border,
+ int imageSize,
+ java.nio.Buffer data
+ ){}
+
+ public void glCompressedTexSubImage2D(
+ int target,
+ int level,
+ int xoffset,
+ int yoffset,
+ int width,
+ int height,
+ int format,
+ int imageSize,
+ java.nio.Buffer data
+ ){}
+
+ public void glCopyTexImage2D(
+ int target,
+ int level,
+ int internalformat,
+ int x,
+ int y,
+ int width,
+ int height,
+ int border
+ ){}
+
+ public void glCopyTexSubImage2D(
+ int target,
+ int level,
+ int xoffset,
+ int yoffset,
+ int x,
+ int y,
+ int width,
+ int height
+ ){}
+
+ public void glCullFace(
+ int mode
+ ){}
+
+ public void glDeleteTextures(
+ int n,
+ int[] textures,
+ int offset
+ ){}
+
+ public void glDeleteTextures(
+ int n,
+ java.nio.IntBuffer textures
+ ){}
+
+ public void glDepthFunc(
+ int func
+ ){}
+
+ public void glDepthMask(
+ boolean flag
+ ){}
+
+ public void glDepthRangef(
+ float zNear,
+ float zFar
+ ){}
+
+ public void glDepthRangex(
+ int zNear,
+ int zFar
+ ){}
+
+ public void glDisable(
+ int cap
+ ){}
+
+ public void glDisableClientState(
+ int array
+ ){}
+
+ public void glDrawArrays(
+ int mode,
+ int first,
+ int count
+ ){}
+
+ public void glDrawElements(
+ int mode,
+ int count,
+ int type,
+ java.nio.Buffer indices
+ ){}
+
+ public void glEnable(
+ int cap
+ ){}
+
+ public void glEnableClientState(
+ int array
+ ){}
+
+ public void glFinish(
+ ){}
+
+ public void glFlush(
+ ){}
+
+ public void glFogf(
+ int pname,
+ float param
+ ){}
+
+ public void glFogfv(
+ int pname,
+ float[] params,
+ int offset
+ ){}
+
+ public void glFogfv(
+ int pname,
+ java.nio.FloatBuffer params
+ ){}
+
+ public void glFogx(
+ int pname,
+ int param
+ ){}
+
+ public void glFogxv(
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glFogxv(
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public void glFrontFace(
+ int mode
+ ){}
+
+ public void glFrustumf(
+ float left,
+ float right,
+ float bottom,
+ float top,
+ float zNear,
+ float zFar
+ ){}
+
+ public void glFrustumx(
+ int left,
+ int right,
+ int bottom,
+ int top,
+ int zNear,
+ int zFar
+ ){}
+
+ public void glGenTextures(
+ int n,
+ int[] textures,
+ int offset
+ ){}
+
+ public void glGenTextures(
+ int n,
+ java.nio.IntBuffer textures
+ ){}
+
+ public int glGetError(
+ ){ throw new UnsupportedOperationException(); }
+
+ public void glGetIntegerv(
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glGetIntegerv(
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public String glGetString(
+ int name
+ ){ throw new UnsupportedOperationException(); }
+
+ public void glHint(
+ int target,
+ int mode
+ ){}
+
+ public void glLightModelf(
+ int pname,
+ float param
+ ){}
+
+ public void glLightModelfv(
+ int pname,
+ float[] params,
+ int offset
+ ){}
+
+ public void glLightModelfv(
+ int pname,
+ java.nio.FloatBuffer params
+ ){}
+
+ public void glLightModelx(
+ int pname,
+ int param
+ ){}
+
+ public void glLightModelxv(
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glLightModelxv(
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public void glLightf(
+ int light,
+ int pname,
+ float param
+ ){}
+
+ public void glLightfv(
+ int light,
+ int pname,
+ float[] params,
+ int offset
+ ){}
+
+ public void glLightfv(
+ int light,
+ int pname,
+ java.nio.FloatBuffer params
+ ){}
+
+ public void glLightx(
+ int light,
+ int pname,
+ int param
+ ){}
+
+ public void glLightxv(
+ int light,
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glLightxv(
+ int light,
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public void glLineWidth(
+ float width
+ ){}
+
+ public void glLineWidthx(
+ int width
+ ){}
+
+ public void glLoadIdentity(
+ ){}
+
+ public void glLoadMatrixf(
+ float[] m,
+ int offset
+ ){}
+
+ public void glLoadMatrixf(
+ java.nio.FloatBuffer m
+ ){}
+
+ public void glLoadMatrixx(
+ int[] m,
+ int offset
+ ){}
+
+ public void glLoadMatrixx(
+ java.nio.IntBuffer m
+ ){}
+
+ public void glLogicOp(
+ int opcode
+ ){}
+
+ public void glMaterialf(
+ int face,
+ int pname,
+ float param
+ ){}
+
+ public void glMaterialfv(
+ int face,
+ int pname,
+ float[] params,
+ int offset
+ ){}
+
+ public void glMaterialfv(
+ int face,
+ int pname,
+ java.nio.FloatBuffer params
+ ){}
+
+ public void glMaterialx(
+ int face,
+ int pname,
+ int param
+ ){}
+
+ public void glMaterialxv(
+ int face,
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glMaterialxv(
+ int face,
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public void glMatrixMode(
+ int mode
+ ){}
+
+ public void glMultMatrixf(
+ float[] m,
+ int offset
+ ){}
+
+ public void glMultMatrixf(
+ java.nio.FloatBuffer m
+ ){}
+
+ public void glMultMatrixx(
+ int[] m,
+ int offset
+ ){}
+
+ public void glMultMatrixx(
+ java.nio.IntBuffer m
+ ){}
+
+ public void glMultiTexCoord4f(
+ int target,
+ float s,
+ float t,
+ float r,
+ float q
+ ){}
+
+ public void glMultiTexCoord4x(
+ int target,
+ int s,
+ int t,
+ int r,
+ int q
+ ){}
+
+ public void glNormal3f(
+ float nx,
+ float ny,
+ float nz
+ ){}
+
+ public void glNormal3x(
+ int nx,
+ int ny,
+ int nz
+ ){}
+
+ public void glNormalPointer(
+ int type,
+ int stride,
+ java.nio.Buffer pointer
+ ){}
+
+ public void glOrthof(
+ float left,
+ float right,
+ float bottom,
+ float top,
+ float zNear,
+ float zFar
+ ){}
+
+ public void glOrthox(
+ int left,
+ int right,
+ int bottom,
+ int top,
+ int zNear,
+ int zFar
+ ){}
+
+ public void glPixelStorei(
+ int pname,
+ int param
+ ){}
+
+ public void glPointSize(
+ float size
+ ){}
+
+ public void glPointSizex(
+ int size
+ ){}
+
+ public void glPolygonOffset(
+ float factor,
+ float units
+ ){}
+
+ public void glPolygonOffsetx(
+ int factor,
+ int units
+ ){}
+
+ public void glPopMatrix(
+ ){}
+
+ public void glPushMatrix(
+ ){}
+
+ public void glReadPixels(
+ int x,
+ int y,
+ int width,
+ int height,
+ int format,
+ int type,
+ java.nio.Buffer pixels
+ ){}
+
+ public void glRotatef(
+ float angle,
+ float x,
+ float y,
+ float z
+ ){}
+
+ public void glRotatex(
+ int angle,
+ int x,
+ int y,
+ int z
+ ){}
+
+ public void glSampleCoverage(
+ float value,
+ boolean invert
+ ){}
+
+ public void glSampleCoveragex(
+ int value,
+ boolean invert
+ ){}
+
+ public void glScalef(
+ float x,
+ float y,
+ float z
+ ){}
+
+ public void glScalex(
+ int x,
+ int y,
+ int z
+ ){}
+
+ public void glScissor(
+ int x,
+ int y,
+ int width,
+ int height
+ ){}
+
+ public void glShadeModel(
+ int mode
+ ){}
+
+ public void glStencilFunc(
+ int func,
+ int ref,
+ int mask
+ ){}
+
+ public void glStencilMask(
+ int mask
+ ){}
+
+ public void glStencilOp(
+ int fail,
+ int zfail,
+ int zpass
+ ){}
+
+ public void glTexCoordPointer(
+ int size,
+ int type,
+ int stride,
+ java.nio.Buffer pointer
+ ){}
+
+ public void glTexEnvf(
+ int target,
+ int pname,
+ float param
+ ){}
+
+ public void glTexEnvfv(
+ int target,
+ int pname,
+ float[] params,
+ int offset
+ ){}
+
+ public void glTexEnvfv(
+ int target,
+ int pname,
+ java.nio.FloatBuffer params
+ ){}
+
+ public void glTexEnvx(
+ int target,
+ int pname,
+ int param
+ ){}
+
+ public void glTexEnvxv(
+ int target,
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glTexEnvxv(
+ int target,
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public void glTexImage2D(
+ int target,
+ int level,
+ int internalformat,
+ int width,
+ int height,
+ int border,
+ int format,
+ int type,
+ java.nio.Buffer pixels
+ ){}
+
+ public void glTexParameterf(
+ int target,
+ int pname,
+ float param
+ ){}
+
+ public void glTexParameterx(
+ int target,
+ int pname,
+ int param
+ ){}
+
+ public void glTexSubImage2D(
+ int target,
+ int level,
+ int xoffset,
+ int yoffset,
+ int width,
+ int height,
+ int format,
+ int type,
+ java.nio.Buffer pixels
+ ){}
+
+ public void glTranslatef(
+ float x,
+ float y,
+ float z
+ ){}
+
+ public void glTranslatex(
+ int x,
+ int y,
+ int z
+ ){}
+
+ public void glVertexPointer(
+ int size,
+ int type,
+ int stride,
+ java.nio.Buffer pointer
+ ){}
+
+ public void glViewport(
+ int x,
+ int y,
+ int width,
+ int height
+ ){}
+
+ public int glQueryMatrixxOES(
+ int[] mantissa,
+ int mantissaOffset,
+ int[] exponent,
+ int exponentOffset
+ ){ throw new UnsupportedOperationException(); }
+
+ public int glQueryMatrixxOES(
+ java.nio.IntBuffer mantissa,
+ java.nio.IntBuffer exponent
+ ){ throw new UnsupportedOperationException(); }
+
+ public void glGetPointerv(int pname, java.nio.Buffer[] params){}
+ public void glBindBuffer(
+ int target,
+ int buffer
+ ){}
+
+ public void glBufferData(
+ int target,
+ int size,
+ java.nio.Buffer data,
+ int usage
+ ){}
+
+ public void glBufferSubData(
+ int target,
+ int offset,
+ int size,
+ java.nio.Buffer data
+ ){}
+
+ public void glClipPlanef(
+ int plane,
+ float[] equation,
+ int offset
+ ){}
+
+ public void glClipPlanef(
+ int plane,
+ java.nio.FloatBuffer equation
+ ){}
+
+ public void glClipPlanex(
+ int plane,
+ int[] equation,
+ int offset
+ ){}
+
+ public void glClipPlanex(
+ int plane,
+ java.nio.IntBuffer equation
+ ){}
+
+ public void glColor4ub(
+ byte red,
+ byte green,
+ byte blue,
+ byte alpha
+ ){}
+
+ public void glColorPointer(
+ int size,
+ int type,
+ int stride,
+ int offset
+ ){}
+
+ public void glDeleteBuffers(
+ int n,
+ int[] buffers,
+ int offset
+ ){}
+
+ public void glDeleteBuffers(
+ int n,
+ java.nio.IntBuffer buffers
+ ){}
+
+ public void glDrawElements(
+ int mode,
+ int count,
+ int type,
+ int offset
+ ){}
+
+ public void glGenBuffers(
+ int n,
+ int[] buffers,
+ int offset
+ ){}
+
+ public void glGenBuffers(
+ int n,
+ java.nio.IntBuffer buffers
+ ){}
+
+ public void glGetBooleanv(
+ int pname,
+ boolean[] params,
+ int offset
+ ){}
+
+ public void glGetBooleanv(
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public void glGetBufferParameteriv(
+ int target,
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glGetBufferParameteriv(
+ int target,
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public void glGetClipPlanef(
+ int pname,
+ float[] eqn,
+ int offset
+ ){}
+
+ public void glGetClipPlanef(
+ int pname,
+ java.nio.FloatBuffer eqn
+ ){}
+
+ public void glGetClipPlanex(
+ int pname,
+ int[] eqn,
+ int offset
+ ){}
+
+ public void glGetClipPlanex(
+ int pname,
+ java.nio.IntBuffer eqn
+ ){}
+
+ public void glGetFixedv(
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glGetFixedv(
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public void glGetFloatv(
+ int pname,
+ float[] params,
+ int offset
+ ){}
+
+ public void glGetFloatv(
+ int pname,
+ java.nio.FloatBuffer params
+ ){}
+
+ public void glGetLightfv(
+ int light,
+ int pname,
+ float[] params,
+ int offset
+ ){}
+
+ public void glGetLightfv(
+ int light,
+ int pname,
+ java.nio.FloatBuffer params
+ ){}
+
+ public void glGetLightxv(
+ int light,
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glGetLightxv(
+ int light,
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public void glGetMaterialfv(
+ int face,
+ int pname,
+ float[] params,
+ int offset
+ ){}
+
+ public void glGetMaterialfv(
+ int face,
+ int pname,
+ java.nio.FloatBuffer params
+ ){}
+
+ public void glGetMaterialxv(
+ int face,
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glGetMaterialxv(
+ int face,
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public void glGetTexEnviv(
+ int env,
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glGetTexEnviv(
+ int env,
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public void glGetTexEnvxv(
+ int env,
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glGetTexEnvxv(
+ int env,
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public void glGetTexParameterfv(
+ int target,
+ int pname,
+ float[] params,
+ int offset
+ ){}
+
+ public void glGetTexParameterfv(
+ int target,
+ int pname,
+ java.nio.FloatBuffer params
+ ){}
+
+ public void glGetTexParameteriv(
+ int target,
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glGetTexParameteriv(
+ int target,
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public void glGetTexParameterxv(
+ int target,
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glGetTexParameterxv(
+ int target,
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public boolean glIsBuffer(
+ int buffer
+ ){ throw new UnsupportedOperationException(); }
+
+ public boolean glIsEnabled(
+ int cap
+ ){ throw new UnsupportedOperationException(); }
+
+ public boolean glIsTexture(
+ int texture
+ ){ throw new UnsupportedOperationException(); }
+
+ public void glNormalPointer(
+ int type,
+ int stride,
+ int offset
+ ){}
+
+ public void glPointParameterf(
+ int pname,
+ float param
+ ){}
+
+ public void glPointParameterfv(
+ int pname,
+ float[] params,
+ int offset
+ ){}
+
+ public void glPointParameterfv(
+ int pname,
+ java.nio.FloatBuffer params
+ ){}
+
+ public void glPointParameterx(
+ int pname,
+ int param
+ ){}
+
+ public void glPointParameterxv(
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glPointParameterxv(
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public void glPointSizePointerOES(
+ int type,
+ int stride,
+ java.nio.Buffer pointer
+ ){}
+
+ public void glTexCoordPointer(
+ int size,
+ int type,
+ int stride,
+ int offset
+ ){}
+
+ public void glTexEnvi(
+ int target,
+ int pname,
+ int param
+ ){}
+
+ public void glTexEnviv(
+ int target,
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glTexEnviv(
+ int target,
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public void glTexParameterfv(
+ int target,
+ int pname,
+ float[] params,
+ int offset
+ ){}
+
+ public void glTexParameterfv(
+ int target,
+ int pname,
+ java.nio.FloatBuffer params
+ ){}
+
+ public void glTexParameteri(
+ int target,
+ int pname,
+ int param
+ ){}
+
+ public void glTexParameteriv(
+ int target,
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glTexParameteriv(
+ int target,
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public void glTexParameterxv(
+ int target,
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glTexParameterxv(
+ int target,
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public void glVertexPointer(
+ int size,
+ int type,
+ int stride,
+ int offset
+ ){}
+
+ public void glCurrentPaletteMatrixOES(
+ int matrixpaletteindex
+ ){}
+
+ public void glDrawTexfOES(
+ float x,
+ float y,
+ float z,
+ float width,
+ float height
+ ){}
+
+ public void glDrawTexfvOES(
+ float[] coords,
+ int offset
+ ){}
+
+ public void glDrawTexfvOES(
+ java.nio.FloatBuffer coords
+ ){}
+
+ public void glDrawTexiOES(
+ int x,
+ int y,
+ int z,
+ int width,
+ int height
+ ){}
+
+ public void glDrawTexivOES(
+ int[] coords,
+ int offset
+ ){}
+
+ public void glDrawTexivOES(
+ java.nio.IntBuffer coords
+ ){}
+
+ public void glDrawTexsOES(
+ short x,
+ short y,
+ short z,
+ short width,
+ short height
+ ){}
+
+ public void glDrawTexsvOES(
+ short[] coords,
+ int offset
+ ){}
+
+ public void glDrawTexsvOES(
+ java.nio.ShortBuffer coords
+ ){}
+
+ public void glDrawTexxOES(
+ int x,
+ int y,
+ int z,
+ int width,
+ int height
+ ){}
+
+ public void glDrawTexxvOES(
+ int[] coords,
+ int offset
+ ){}
+
+ public void glDrawTexxvOES(
+ java.nio.IntBuffer coords
+ ){}
+
+ public void glLoadPaletteFromModelViewMatrixOES(
+ ){}
+
+ public void glMatrixIndexPointerOES(
+ int size,
+ int type,
+ int stride,
+ java.nio.Buffer pointer
+ ){}
+
+ public void glMatrixIndexPointerOES(
+ int size,
+ int type,
+ int stride,
+ int offset
+ ){}
+
+ public void glWeightPointerOES(
+ int size,
+ int type,
+ int stride,
+ java.nio.Buffer pointer
+ ){}
+
+ public void glWeightPointerOES(
+ int size,
+ int type,
+ int stride,
+ int offset
+ ){}
+
+ public void glBindFramebufferOES(
+ int target,
+ int framebuffer
+ ){}
+
+ public void glBindRenderbufferOES(
+ int target,
+ int renderbuffer
+ ){}
+
+ public void glBlendEquation(
+ int mode
+ ){}
+
+ public void glBlendEquationSeparate(
+ int modeRGB,
+ int modeAlpha
+ ){}
+
+ public void glBlendFuncSeparate(
+ int srcRGB,
+ int dstRGB,
+ int srcAlpha,
+ int dstAlpha
+ ){}
+
+ public int glCheckFramebufferStatusOES(
+ int target
+ ){ throw new UnsupportedOperationException(); }
+
+ public void glDeleteFramebuffersOES(
+ int n,
+ int[] framebuffers,
+ int offset
+ ){}
+
+ public void glDeleteFramebuffersOES(
+ int n,
+ java.nio.IntBuffer framebuffers
+ ){}
+
+ public void glDeleteRenderbuffersOES(
+ int n,
+ int[] renderbuffers,
+ int offset
+ ){}
+
+ public void glDeleteRenderbuffersOES(
+ int n,
+ java.nio.IntBuffer renderbuffers
+ ){}
+
+ public void glFramebufferRenderbufferOES(
+ int target,
+ int attachment,
+ int renderbuffertarget,
+ int renderbuffer
+ ){}
+
+ public void glFramebufferTexture2DOES(
+ int target,
+ int attachment,
+ int textarget,
+ int texture,
+ int level
+ ){}
+
+ public void glGenerateMipmapOES(
+ int target
+ ){}
+
+ public void glGenFramebuffersOES(
+ int n,
+ int[] framebuffers,
+ int offset
+ ){}
+
+ public void glGenFramebuffersOES(
+ int n,
+ java.nio.IntBuffer framebuffers
+ ){}
+
+ public void glGenRenderbuffersOES(
+ int n,
+ int[] renderbuffers,
+ int offset
+ ){}
+
+ public void glGenRenderbuffersOES(
+ int n,
+ java.nio.IntBuffer renderbuffers
+ ){}
+
+ public void glGetFramebufferAttachmentParameterivOES(
+ int target,
+ int attachment,
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glGetFramebufferAttachmentParameterivOES(
+ int target,
+ int attachment,
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public void glGetRenderbufferParameterivOES(
+ int target,
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glGetRenderbufferParameterivOES(
+ int target,
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public void glGetTexGenfv(
+ int coord,
+ int pname,
+ float[] params,
+ int offset
+ ){}
+
+ public void glGetTexGenfv(
+ int coord,
+ int pname,
+ java.nio.FloatBuffer params
+ ){}
+
+ public void glGetTexGeniv(
+ int coord,
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glGetTexGeniv(
+ int coord,
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public void glGetTexGenxv(
+ int coord,
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glGetTexGenxv(
+ int coord,
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public boolean glIsFramebufferOES(
+ int framebuffer
+ ){ throw new UnsupportedOperationException(); }
+
+ public boolean glIsRenderbufferOES(
+ int renderbuffer
+ ){ throw new UnsupportedOperationException(); }
+
+ public void glRenderbufferStorageOES(
+ int target,
+ int internalformat,
+ int width,
+ int height
+ ){}
+
+ public void glTexGenf(
+ int coord,
+ int pname,
+ float param
+ ){}
+
+ public void glTexGenfv(
+ int coord,
+ int pname,
+ float[] params,
+ int offset
+ ){}
+
+ public void glTexGenfv(
+ int coord,
+ int pname,
+ java.nio.FloatBuffer params
+ ){}
+
+ public void glTexGeni(
+ int coord,
+ int pname,
+ int param
+ ){}
+
+ public void glTexGeniv(
+ int coord,
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glTexGeniv(
+ int coord,
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+
+ public void glTexGenx(
+ int coord,
+ int pname,
+ int param
+ ){}
+
+ public void glTexGenxv(
+ int coord,
+ int pname,
+ int[] params,
+ int offset
+ ){}
+
+ public void glTexGenxv(
+ int coord,
+ int pname,
+ java.nio.IntBuffer params
+ ){}
+}
diff --git a/tests/src/com/android/gallery3d/glrenderer/TextureTest.java b/tests/src/com/android/gallery3d/glrenderer/TextureTest.java
new file mode 100644
index 0000000..9e79554
--- /dev/null
+++ b/tests/src/com/android/gallery3d/glrenderer/TextureTest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2010 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.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.gallery3d.glrenderer.BasicTexture;
+import com.android.gallery3d.glrenderer.BitmapTexture;
+import com.android.gallery3d.glrenderer.ColorTexture;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.GLES11Canvas;
+import com.android.gallery3d.glrenderer.UploadedTexture;
+
+import junit.framework.TestCase;
+
+import javax.microedition.khronos.opengles.GL11;
+
+public class TextureTest extends TestCase {
+ @SuppressWarnings("unused")
+ private static final String TAG = "TextureTest";
+
+ class MyBasicTexture extends BasicTexture {
+ int mOnBindCalled;
+ int mOpaqueCalled;
+
+ MyBasicTexture(GLCanvas canvas, int id) {
+ super(canvas, id, 0);
+ }
+
+ @Override
+ protected boolean onBind(GLCanvas canvas) {
+ mOnBindCalled++;
+ return true;
+ }
+
+ @Override
+ protected int getTarget() {
+ return GL11.GL_TEXTURE_2D;
+ }
+
+ @Override
+ public boolean isOpaque() {
+ mOpaqueCalled++;
+ return true;
+ }
+
+ void upload() {
+ mState = STATE_LOADED;
+ }
+ }
+
+ @SmallTest
+ public void testBasicTexture() {
+ GL11 glStub = new GLStub();
+ GLCanvas canvas = new GLES11Canvas(glStub);
+ MyBasicTexture texture = new MyBasicTexture(canvas, 47);
+
+ assertEquals(47, texture.getId());
+ texture.setSize(1, 1);
+ assertEquals(1, texture.getWidth());
+ assertEquals(1, texture.getHeight());
+ assertEquals(1, texture.getTextureWidth());
+ assertEquals(1, texture.getTextureHeight());
+ texture.setSize(3, 5);
+ assertEquals(3, texture.getWidth());
+ assertEquals(5, texture.getHeight());
+ assertEquals(4, texture.getTextureWidth());
+ assertEquals(8, texture.getTextureHeight());
+
+ assertFalse(texture.isLoaded());
+ texture.upload();
+ assertTrue(texture.isLoaded());
+
+ // For a different GL, it's not loaded.
+ GLCanvas canvas2 = new GLES11Canvas(glStub);
+ assertFalse(texture.isLoaded());
+
+ assertEquals(0, texture.mOnBindCalled);
+ assertEquals(0, texture.mOpaqueCalled);
+ texture.draw(canvas, 100, 200, 1, 1);
+ assertEquals(1, texture.mOnBindCalled);
+ assertEquals(1, texture.mOpaqueCalled);
+ texture.draw(canvas, 0, 0);
+ assertEquals(2, texture.mOnBindCalled);
+ assertEquals(2, texture.mOpaqueCalled);
+ }
+
+ @SmallTest
+ public void testColorTexture() {
+ GLCanvasMock canvas = new GLCanvasMock();
+ ColorTexture texture = new ColorTexture(0x12345678);
+
+ texture.setSize(42, 47);
+ assertEquals(texture.getWidth(), 42);
+ assertEquals(texture.getHeight(), 47);
+ assertEquals(0, canvas.mFillRectCalled);
+ texture.draw(canvas, 0, 0);
+ assertEquals(1, canvas.mFillRectCalled);
+ assertEquals(0x12345678, canvas.mFillRectColor);
+ assertEquals(42f, canvas.mFillRectWidth);
+ assertEquals(47f, canvas.mFillRectHeight);
+ assertFalse(texture.isOpaque());
+ assertTrue(new ColorTexture(0xFF000000).isOpaque());
+ }
+
+ private class MyUploadedTexture extends UploadedTexture {
+ int mGetCalled;
+ int mFreeCalled;
+ Bitmap mBitmap;
+ @Override
+ protected Bitmap onGetBitmap() {
+ mGetCalled++;
+ Config config = Config.ARGB_8888;
+ mBitmap = Bitmap.createBitmap(47, 42, config);
+ return mBitmap;
+ }
+ @Override
+ protected void onFreeBitmap(Bitmap bitmap) {
+ mFreeCalled++;
+ assertSame(mBitmap, bitmap);
+ mBitmap.recycle();
+ mBitmap = null;
+ }
+ }
+
+ @SmallTest
+ public void testUploadedTexture() {
+ GL11 glStub = new GLStub();
+ GLCanvas canvas = new GLES11Canvas(glStub);
+ MyUploadedTexture texture = new MyUploadedTexture();
+
+ // draw it and the bitmap should be fetched.
+ assertEquals(0, texture.mFreeCalled);
+ assertEquals(0, texture.mGetCalled);
+ texture.draw(canvas, 0, 0);
+ assertEquals(1, texture.mGetCalled);
+ assertTrue(texture.isLoaded());
+ assertTrue(texture.isContentValid());
+
+ // invalidate content and it should be freed.
+ texture.invalidateContent();
+ assertFalse(texture.isContentValid());
+ assertEquals(1, texture.mFreeCalled);
+ assertTrue(texture.isLoaded()); // But it's still loaded
+
+ // draw it again and the bitmap should be fetched again.
+ texture.draw(canvas, 0, 0);
+ assertEquals(2, texture.mGetCalled);
+ assertTrue(texture.isLoaded());
+ assertTrue(texture.isContentValid());
+
+ // recycle the texture and it should be freed again.
+ texture.recycle();
+ assertEquals(2, texture.mFreeCalled);
+ // TODO: these two are broken and waiting for fix.
+ //assertFalse(texture.isLoaded(canvas));
+ //assertFalse(texture.isContentValid(canvas));
+ }
+
+ class MyTextureForMixed extends BasicTexture {
+ MyTextureForMixed(GLCanvas canvas, int id) {
+ super(canvas, id, 0);
+ }
+
+ @Override
+ protected boolean onBind(GLCanvas canvas) {
+ return true;
+ }
+
+ @Override
+ protected int getTarget() {
+ return GL11.GL_TEXTURE_2D;
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return true;
+ }
+ }
+
+ @SmallTest
+ public void testBitmapTexture() {
+ Config config = Config.ARGB_8888;
+ Bitmap bitmap = Bitmap.createBitmap(47, 42, config);
+ assertFalse(bitmap.isRecycled());
+ BitmapTexture texture = new BitmapTexture(bitmap);
+ texture.recycle();
+ assertFalse(bitmap.isRecycled());
+ bitmap.recycle();
+ assertTrue(bitmap.isRecycled());
+ }
+}
diff --git a/tests/src/com/android/gallery3d/jpegstream/JpegStreamReaderTest.java b/tests/src/com/android/gallery3d/jpegstream/JpegStreamReaderTest.java
new file mode 100644
index 0000000..ae60a91
--- /dev/null
+++ b/tests/src/com/android/gallery3d/jpegstream/JpegStreamReaderTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2013 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.jpegstream;
+
+import android.test.suitebuilder.annotation.MediumTest;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Point;
+import android.os.Environment;
+import android.util.Log;
+
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.tests.R;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+public class JpegStreamReaderTest extends JpegStreamTestCase {
+ public static final String TAG = "JpegStreamReaderTest";
+ private JPEGInputStream mStream;
+ private Bitmap mBitmap;
+
+ public JpegStreamReaderTest(int imageRes) {
+ super(imageRes);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mBitmap = BitmapFactory.decodeStream(getImageInputStream());
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ Utils.closeSilently(mStream);
+ mStream = null;
+ if (mBitmap != null) {
+ mBitmap.recycle();
+ mBitmap = null;
+ }
+ }
+
+ @MediumTest
+ public void testBasicReads() throws Exception {
+
+ // Setup input stream.
+ mStream = new JPEGInputStream(reopenFileStream(), JpegConfig.FORMAT_RGBA);
+ Point dimens = mStream.getDimensions();
+
+ // Read whole stream into array.
+ byte[] bytes = new byte[dimens.x * StreamUtils.pixelSize(JpegConfig.FORMAT_RGBA) * dimens.y];
+ assertTrue(mStream.read(bytes, 0, bytes.length) == bytes.length);
+
+ // Set pixels in bitmap
+ Bitmap test = Bitmap.createBitmap(dimens.x, dimens.y, Bitmap.Config.ARGB_8888);
+ ByteBuffer buf = ByteBuffer.wrap(bytes);
+ test.copyPixelsFromBuffer(buf);
+ assertTrue(test.getWidth() == mBitmap.getWidth() && test.getHeight() == mBitmap.getHeight());
+ assertTrue(mStream.read(bytes, 0, bytes.length) == -1);
+ }
+
+ // TODO : more tests
+}
diff --git a/tests/src/com/android/gallery3d/jpegstream/JpegStreamTestCase.java b/tests/src/com/android/gallery3d/jpegstream/JpegStreamTestCase.java
new file mode 100644
index 0000000..ed3b08a
--- /dev/null
+++ b/tests/src/com/android/gallery3d/jpegstream/JpegStreamTestCase.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2013 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.jpegstream;
+
+import android.content.res.Resources;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import com.android.gallery3d.common.Utils;
+
+import java.io.InputStream;
+
+public class JpegStreamTestCase extends InstrumentationTestCase {
+ public static final String TAG = "JpegStreamTestCase";
+
+ private static final String RES_ID_TITLE = "Resource ID: %x";
+
+ private InputStream mImageInputStream;
+ private final int mImageResourceId;
+
+ public JpegStreamTestCase(int imageRes) {
+ mImageResourceId = imageRes;
+ }
+
+ protected InputStream getImageInputStream() {
+ return mImageInputStream;
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ Log.d(TAG, "doing setUp...");
+ mImageInputStream = reopenFileStream();
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ Log.d(TAG, "doing tearDown...");
+ Utils.closeSilently(mImageInputStream);
+ mImageInputStream = null;
+ }
+
+ protected String getImageTitle() {
+ return String.format(RES_ID_TITLE, mImageResourceId);
+ }
+
+ protected InputStream reopenFileStream() throws Exception {
+ return openResource(mImageResourceId);
+ }
+
+ protected InputStream openResource(int resourceID) throws Exception {
+ try {
+ Resources res = getInstrumentation().getContext().getResources();
+ return res.openRawResource(resourceID);
+ } catch (Exception e) {
+ throw new Exception(getImageTitle(), e);
+ }
+ }
+}
diff --git a/tests/src/com/android/gallery3d/jpegstream/JpegStreamTestRunner.java b/tests/src/com/android/gallery3d/jpegstream/JpegStreamTestRunner.java
new file mode 100644
index 0000000..2afaf39
--- /dev/null
+++ b/tests/src/com/android/gallery3d/jpegstream/JpegStreamTestRunner.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2013 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.jpegstream;
+
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+import android.util.Log;
+
+import com.android.gallery3d.exif.ExifTestRunner;
+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 JpegStreamTestRunner extends InstrumentationTestRunner {
+ private static final String TAG = "JpegStreamTestRunner";
+
+ private static final int[] IMG_RESOURCE = {
+ R.raw.galaxy_nexus
+ };
+
+ @Override
+ public TestSuite getAllTests() {
+ TestSuite suite = new InstrumentationTestSuite(this);
+ addAllTestsFromTestCase(JpegStreamReaderTest.class, suite);
+ addAllTestsFromTestCase(JpegStreamWriterTest.class, suite);
+ return suite;
+ }
+
+ private void addAllTestsFromTestCase(Class<? extends JpegStreamTestCase> 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;
+ try {
+ test = testClass.getDeclaredConstructor(int.class).
+ newInstance(IMG_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);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public ClassLoader getLoader() {
+ return ExifTestRunner.class.getClassLoader();
+ }
+}
diff --git a/tests/src/com/android/gallery3d/jpegstream/JpegStreamWriterTest.java b/tests/src/com/android/gallery3d/jpegstream/JpegStreamWriterTest.java
new file mode 100644
index 0000000..befba4c
--- /dev/null
+++ b/tests/src/com/android/gallery3d/jpegstream/JpegStreamWriterTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2013 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.jpegstream;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Environment;
+import android.util.Log;
+
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.tests.R;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.nio.ByteBuffer;
+
+public class JpegStreamWriterTest extends JpegStreamTestCase {
+ public static final String TAG = "JpegStreamWriterTest";
+ private JPEGOutputStream mStream;
+ private ByteArrayOutputStream mWrappedStream;
+ private Bitmap mBitmap;
+ private Bitmap mControl;
+
+ // galaxy_nexus.jpg image compressed with q=20
+ private static final int CONTROL_RID = R.raw.jpeg_control;
+
+ public JpegStreamWriterTest(int imageRes) {
+ super(imageRes);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mBitmap = BitmapFactory.decodeStream(getImageInputStream());
+ mControl = BitmapFactory.decodeStream(openResource(CONTROL_RID));
+ mWrappedStream = new ByteArrayOutputStream();
+
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ Utils.closeSilently(mStream);
+ Utils.closeSilently(mWrappedStream);
+ mWrappedStream = null;
+ mStream = null;
+ if (mBitmap != null) {
+ mBitmap.recycle();
+ mBitmap = null;
+ }
+ if (mControl != null) {
+ mControl.recycle();
+ mControl = null;
+ }
+ }
+
+ public void testBasicWrites() throws Exception {
+ assertTrue(mBitmap != null);
+ int width = mBitmap.getWidth();
+ int height = mBitmap.getHeight();
+ mStream = new JPEGOutputStream(mWrappedStream, width,
+ height, 20, JpegConfig.FORMAT_RGBA);
+
+ // Put bitmap pixels into a byte array (format is RGBA).
+ int rowLength = width * StreamUtils.pixelSize(JpegConfig.FORMAT_RGBA);
+ int size = height * rowLength;
+ byte[] byteArray = new byte[size];
+ ByteBuffer buf = ByteBuffer.wrap(byteArray);
+ mBitmap.copyPixelsToBuffer(buf);
+
+ // Write out whole array
+ mStream.write(byteArray, 0, byteArray.length);
+ mStream.close();
+
+ // Get compressed jpeg output
+ byte[] compressed = mWrappedStream.toByteArray();
+
+ // Check jpeg
+ ByteArrayInputStream inStream = new ByteArrayInputStream(compressed);
+ Bitmap test = BitmapFactory.decodeStream(inStream);
+ assertTrue(test != null);
+ assertTrue(test.sameAs(mControl));
+ }
+
+ public void testStreamingWrites() throws Exception {
+ assertTrue(mBitmap != null);
+ int width = mBitmap.getWidth();
+ int height = mBitmap.getHeight();
+ mStream = new JPEGOutputStream(mWrappedStream, width,
+ height, 20, JpegConfig.FORMAT_RGBA);
+
+ // Put bitmap pixels into a byte array (format is RGBA).
+ int rowLength = width * StreamUtils.pixelSize(JpegConfig.FORMAT_RGBA);
+ int size = height * rowLength;
+ byte[] byteArray = new byte[size];
+ ByteBuffer buf = ByteBuffer.wrap(byteArray);
+ mBitmap.copyPixelsToBuffer(buf);
+
+ // Write array in chunks
+ int chunkSize = rowLength / 3;
+ int written = 0;
+ while (written < size) {
+ if (written + chunkSize > size) {
+ chunkSize = size - written;
+ }
+ mStream.write(byteArray, written, chunkSize);
+ written += chunkSize;
+ }
+ mStream.close();
+
+ // Get compressed jpeg output
+ byte[] compressed = mWrappedStream.toByteArray();
+
+ // Check jpeg
+ ByteArrayInputStream inStream = new ByteArrayInputStream(compressed);
+ Bitmap test = BitmapFactory.decodeStream(inStream);
+ assertTrue(test != null);
+ assertTrue(test.sameAs(mControl));
+ }
+
+ // TODO : more tests
+}
diff --git a/tests/src/com/android/gallery3d/ui/GLCanvasStub.java b/tests/src/com/android/gallery3d/ui/GLCanvasStub.java
new file mode 100644
index 0000000..01f0350
--- /dev/null
+++ b/tests/src/com/android/gallery3d/ui/GLCanvasStub.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2010 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.ui;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+import com.android.gallery3d.glrenderer.BasicTexture;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.GLId;
+import com.android.gallery3d.glrenderer.GLPaint;
+import com.android.gallery3d.glrenderer.RawTexture;
+
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+
+import javax.microedition.khronos.opengles.GL11;
+
+public class GLCanvasStub implements GLCanvas {
+ @Override
+ public void setSize(int width, int height) {}
+ @Override
+ public void clearBuffer() {}
+ @Override
+ public void clearBuffer(float[] argb) {}
+ public void setCurrentAnimationTimeMillis(long time) {}
+ public long currentAnimationTimeMillis() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public void setAlpha(float alpha) {}
+ @Override
+ public float getAlpha() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public void multiplyAlpha(float alpha) {}
+ @Override
+ public void translate(float x, float y, float z) {}
+ @Override
+ public void translate(float x, float y) {}
+ @Override
+ public void scale(float sx, float sy, float sz) {}
+ @Override
+ public void rotate(float angle, float x, float y, float z) {}
+ public boolean clipRect(int left, int top, int right, int bottom) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public void save() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public void save(int saveFlags) {
+ throw new UnsupportedOperationException();
+ }
+ public void setBlendEnabled(boolean enabled) {}
+ @Override
+ public void restore() {}
+ @Override
+ public void drawLine(float x1, float y1, float x2, float y2, GLPaint paint) {}
+ @Override
+ public void drawRect(float x1, float y1, float x2, float y2, GLPaint paint) {}
+ @Override
+ public void fillRect(float x, float y, float width, float height, int color) {}
+ @Override
+ public void drawTexture(
+ BasicTexture texture, int x, int y, int width, int height) {}
+ @Override
+ public void drawMesh(BasicTexture tex, int x, int y, int xyBuffer,
+ int uvBuffer, int indexBuffer, int indexCount) {}
+ public void drawTexture(BasicTexture texture,
+ int x, int y, int width, int height, float alpha) {}
+ @Override
+ public void drawTexture(BasicTexture texture, RectF source, RectF target) {}
+ @Override
+ public void drawTexture(BasicTexture texture, float[] mTextureTransform,
+ int x, int y, int w, int h) {}
+ public void drawMixed(BasicTexture from, BasicTexture to,
+ float ratio, int x, int y, int w, int h) {}
+ @Override
+ public void drawMixed(BasicTexture from, int to,
+ float ratio, int x, int y, int w, int h) {}
+ public void drawMixed(BasicTexture from, BasicTexture to,
+ float ratio, int x, int y, int width, int height, float alpha) {}
+ public BasicTexture copyTexture(int x, int y, int width, int height) {
+ throw new UnsupportedOperationException();
+ }
+ public GL11 getGLInstance() {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public boolean unloadTexture(BasicTexture texture) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public void deleteBuffer(int bufferId) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public void deleteRecycledResources() {}
+ @Override
+ public void multiplyMatrix(float[] mMatrix, int offset) {}
+ @Override
+ public void dumpStatisticsAndClear() {}
+ @Override
+ public void beginRenderTarget(RawTexture texture) {}
+ @Override
+ public void endRenderTarget() {}
+ @Override
+ public void drawMixed(BasicTexture from, int toColor,
+ float ratio, RectF src, RectF target) {}
+
+ @Override
+ public void setTextureParameters(BasicTexture texture) {
+ }
+ @Override
+ public void initializeTextureSize(BasicTexture texture, int format, int type) {
+ }
+ @Override
+ public void initializeTexture(BasicTexture texture, Bitmap bitmap) {
+ }
+ @Override
+ public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap,
+ int format, int type) {
+ }
+ @Override
+ public int uploadBuffer(ByteBuffer buffer) {
+ return 0;
+ }
+ @Override
+ public int uploadBuffer(FloatBuffer buffer) {
+ return 0;
+ }
+ @Override
+ public void recoverFromLightCycle() {
+ }
+ @Override
+ public void getBounds(Rect bounds, int x, int y, int width, int height) {
+ }
+ @Override
+ public GLId getGLId() {
+ return null;
+ }
+}
diff --git a/tests/src/com/android/gallery3d/ui/GLRootMock.java b/tests/src/com/android/gallery3d/ui/GLRootMock.java
new file mode 100644
index 0000000..da78e14
--- /dev/null
+++ b/tests/src/com/android/gallery3d/ui/GLRootMock.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2010 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.ui;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import com.android.gallery3d.anim.CanvasAnimation;
+
+public class GLRootMock implements GLRoot {
+ int mRequestRenderCalled;
+ int mRequestLayoutContentPaneCalled;
+
+ public void addOnGLIdleListener(OnGLIdleListener listener) {}
+ public void registerLaunchedAnimation(CanvasAnimation animation) {}
+ public void requestRenderForced() {
+ mRequestRenderCalled++;
+ }
+ public void requestRender() {
+ mRequestRenderCalled++;
+ }
+ public void requestLayoutContentPane() {
+ mRequestLayoutContentPaneCalled++;
+ }
+ public boolean hasStencil() { return true; }
+ public void lockRenderThread() {}
+ public void unlockRenderThread() {}
+ public void setContentPane(GLView content) {}
+ public void setOrientationSource(OrientationSource source) {}
+ public int getDisplayRotation() { return 0; }
+ public int getCompensation() { return 0; }
+ public Matrix getCompensationMatrix() { return null; }
+ public void freeze() {}
+ public void unfreeze() {}
+ public void setLightsOutMode(boolean enabled) {}
+ public Context getContext() { return null; }
+}
diff --git a/tests/src/com/android/gallery3d/ui/GLRootStub.java b/tests/src/com/android/gallery3d/ui/GLRootStub.java
new file mode 100644
index 0000000..25e7bca
--- /dev/null
+++ b/tests/src/com/android/gallery3d/ui/GLRootStub.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2010 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.ui;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import com.android.gallery3d.anim.CanvasAnimation;
+
+public class GLRootStub implements GLRoot {
+ public void addOnGLIdleListener(OnGLIdleListener listener) {}
+ public void registerLaunchedAnimation(CanvasAnimation animation) {}
+ public void requestRenderForced() {}
+ public void requestRender() {}
+ public void requestLayoutContentPane() {}
+ public boolean hasStencil() { return true; }
+ public void lockRenderThread() {}
+ public void unlockRenderThread() {}
+ public void setContentPane(GLView content) {}
+ public void setOrientationSource(OrientationSource source) {}
+ public int getDisplayRotation() { return 0; }
+ public int getCompensation() { return 0; }
+ public Matrix getCompensationMatrix() { return null; }
+ public void freeze() {}
+ public void unfreeze() {}
+ public void setLightsOutMode(boolean enabled) {}
+ public Context getContext() { return null; }
+}
diff --git a/tests/src/com/android/gallery3d/ui/GLViewMock.java b/tests/src/com/android/gallery3d/ui/GLViewMock.java
new file mode 100644
index 0000000..9b7488f
--- /dev/null
+++ b/tests/src/com/android/gallery3d/ui/GLViewMock.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2010 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.ui;
+
+import com.android.gallery3d.glrenderer.GLCanvas;
+
+class GLViewMock extends GLView {
+ // onAttachToRoot
+ int mOnAttachCalled;
+ GLRoot mRoot;
+ // onDetachFromRoot
+ int mOnDetachCalled;
+ // onVisibilityChanged
+ int mOnVisibilityChangedCalled;
+ // onLayout
+ int mOnLayoutCalled;
+ boolean mOnLayoutChangeSize;
+ // renderBackground
+ int mRenderBackgroundCalled;
+ // onMeasure
+ int mOnMeasureCalled;
+ int mOnMeasureWidthSpec;
+ int mOnMeasureHeightSpec;
+
+ @Override
+ public void onAttachToRoot(GLRoot root) {
+ mRoot = root;
+ mOnAttachCalled++;
+ super.onAttachToRoot(root);
+ }
+
+ @Override
+ public void onDetachFromRoot() {
+ mRoot = null;
+ mOnDetachCalled++;
+ super.onDetachFromRoot();
+ }
+
+ @Override
+ protected void onVisibilityChanged(int visibility) {
+ mOnVisibilityChangedCalled++;
+ }
+
+ @Override
+ protected void onLayout(boolean changeSize, int left, int top,
+ int right, int bottom) {
+ mOnLayoutCalled++;
+ mOnLayoutChangeSize = changeSize;
+ // call children's layout.
+ for (int i = 0, n = getComponentCount(); i < n; ++i) {
+ GLView item = getComponent(i);
+ item.layout(left, top, right, bottom);
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthSpec, int heightSpec) {
+ mOnMeasureCalled++;
+ mOnMeasureWidthSpec = widthSpec;
+ mOnMeasureHeightSpec = heightSpec;
+ // call children's measure.
+ for (int i = 0, n = getComponentCount(); i < n; ++i) {
+ GLView item = getComponent(i);
+ item.measure(widthSpec, heightSpec);
+ }
+ setMeasuredSize(widthSpec, heightSpec);
+ }
+
+ @Override
+ protected void renderBackground(GLCanvas view) {
+ mRenderBackgroundCalled++;
+ }
+}
diff --git a/tests/src/com/android/gallery3d/ui/GLViewTest.java b/tests/src/com/android/gallery3d/ui/GLViewTest.java
new file mode 100644
index 0000000..b17b254
--- /dev/null
+++ b/tests/src/com/android/gallery3d/ui/GLViewTest.java
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) 2010 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.ui;
+
+import android.graphics.Rect;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.MotionEvent;
+
+import junit.framework.TestCase;
+
+@SmallTest
+public class GLViewTest extends TestCase {
+ @SuppressWarnings("unused")
+ private static final String TAG = "GLViewTest";
+
+ @SmallTest
+ public void testVisibility() {
+ GLViewMock a = new GLViewMock();
+ assertEquals(GLView.VISIBLE, a.getVisibility());
+ assertEquals(0, a.mOnVisibilityChangedCalled);
+ a.setVisibility(GLView.INVISIBLE);
+ assertEquals(GLView.INVISIBLE, a.getVisibility());
+ assertEquals(1, a.mOnVisibilityChangedCalled);
+ a.setVisibility(GLView.VISIBLE);
+ assertEquals(GLView.VISIBLE, a.getVisibility());
+ assertEquals(2, a.mOnVisibilityChangedCalled);
+ }
+
+ @SmallTest
+ public void testComponents() {
+ GLView view = new GLView();
+ assertEquals(0, view.getComponentCount());
+ try {
+ view.getComponent(0);
+ fail();
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+
+ GLView x = new GLView();
+ GLView y = new GLView();
+ view.addComponent(x);
+ view.addComponent(y);
+ assertEquals(2, view.getComponentCount());
+ assertSame(x, view.getComponent(0));
+ assertSame(y, view.getComponent(1));
+ view.removeComponent(x);
+ assertSame(y, view.getComponent(0));
+ try {
+ view.getComponent(1);
+ fail();
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ view.addComponent(y);
+ fail();
+ } catch (IllegalStateException ex) {
+ // expected
+ }
+ view.addComponent(x);
+ view.removeAllComponents();
+ assertEquals(0, view.getComponentCount());
+ }
+
+ @SmallTest
+ public void testBounds() {
+ GLView view = new GLView();
+
+ assertEquals(0, view.getWidth());
+ assertEquals(0, view.getHeight());
+
+ Rect b = view.bounds();
+ assertEquals(0, b.left);
+ assertEquals(0, b.top);
+ assertEquals(0, b.right);
+ assertEquals(0, b.bottom);
+
+ view.layout(10, 20, 30, 100);
+ assertEquals(20, view.getWidth());
+ assertEquals(80, view.getHeight());
+
+ b = view.bounds();
+ assertEquals(10, b.left);
+ assertEquals(20, b.top);
+ assertEquals(30, b.right);
+ assertEquals(100, b.bottom);
+ }
+
+ @SmallTest
+ public void testParent() {
+ GLView a = new GLView();
+ GLView b = new GLView();
+ assertNull(b.mParent);
+ a.addComponent(b);
+ assertSame(a, b.mParent);
+ a.removeComponent(b);
+ assertNull(b.mParent);
+ }
+
+ @SmallTest
+ public void testRoot() {
+ GLViewMock a = new GLViewMock();
+ GLViewMock b = new GLViewMock();
+ GLRoot r = new GLRootStub();
+ GLRoot r2 = new GLRootStub();
+ a.addComponent(b);
+
+ // Attach to root r
+ assertEquals(0, a.mOnAttachCalled);
+ assertEquals(0, b.mOnAttachCalled);
+ a.attachToRoot(r);
+ assertEquals(1, a.mOnAttachCalled);
+ assertEquals(1, b.mOnAttachCalled);
+ assertSame(r, a.getGLRoot());
+ assertSame(r, b.getGLRoot());
+
+ // Detach from r
+ assertEquals(0, a.mOnDetachCalled);
+ assertEquals(0, b.mOnDetachCalled);
+ a.detachFromRoot();
+ assertEquals(1, a.mOnDetachCalled);
+ assertEquals(1, b.mOnDetachCalled);
+
+ // Attach to another root r2
+ assertEquals(1, a.mOnAttachCalled);
+ assertEquals(1, b.mOnAttachCalled);
+ a.attachToRoot(r2);
+ assertEquals(2, a.mOnAttachCalled);
+ assertEquals(2, b.mOnAttachCalled);
+ assertSame(r2, a.getGLRoot());
+ assertSame(r2, b.getGLRoot());
+
+ // Detach from r2
+ assertEquals(1, a.mOnDetachCalled);
+ assertEquals(1, b.mOnDetachCalled);
+ a.detachFromRoot();
+ assertEquals(2, a.mOnDetachCalled);
+ assertEquals(2, b.mOnDetachCalled);
+ }
+
+ @SmallTest
+ public void testRoot2() {
+ GLView a = new GLViewMock();
+ GLViewMock b = new GLViewMock();
+ GLRoot r = new GLRootStub();
+
+ a.attachToRoot(r);
+
+ assertEquals(0, b.mOnAttachCalled);
+ a.addComponent(b);
+ assertEquals(1, b.mOnAttachCalled);
+
+ assertEquals(0, b.mOnDetachCalled);
+ a.removeComponent(b);
+ assertEquals(1, b.mOnDetachCalled);
+ }
+
+ @SmallTest
+ public void testInvalidate() {
+ GLView a = new GLView();
+ GLRootMock r = new GLRootMock();
+ a.attachToRoot(r);
+ assertEquals(0, r.mRequestRenderCalled);
+ a.invalidate();
+ assertEquals(1, r.mRequestRenderCalled);
+ }
+
+ @SmallTest
+ public void testRequestLayout() {
+ GLView a = new GLView();
+ GLView b = new GLView();
+ GLRootMock r = new GLRootMock();
+ a.attachToRoot(r);
+ a.addComponent(b);
+ assertEquals(0, r.mRequestLayoutContentPaneCalled);
+ b.requestLayout();
+ assertEquals(1, r.mRequestLayoutContentPaneCalled);
+ }
+
+ @SmallTest
+ public void testLayout() {
+ GLViewMock a = new GLViewMock();
+ GLViewMock b = new GLViewMock();
+ GLViewMock c = new GLViewMock();
+ GLRootMock r = new GLRootMock();
+
+ a.attachToRoot(r);
+ a.addComponent(b);
+ a.addComponent(c);
+
+ assertEquals(0, a.mOnLayoutCalled);
+ a.layout(10, 20, 60, 100);
+ assertEquals(1, a.mOnLayoutCalled);
+ assertEquals(1, b.mOnLayoutCalled);
+ assertEquals(1, c.mOnLayoutCalled);
+ assertTrue(a.mOnLayoutChangeSize);
+ assertTrue(b.mOnLayoutChangeSize);
+ assertTrue(c.mOnLayoutChangeSize);
+
+ // same size should not trigger onLayout
+ a.layout(10, 20, 60, 100);
+ assertEquals(1, a.mOnLayoutCalled);
+
+ // unless someone requested it, but only those on the path
+ // to the requester.
+ assertEquals(0, r.mRequestLayoutContentPaneCalled);
+ b.requestLayout();
+ a.layout(10, 20, 60, 100);
+ assertEquals(1, r.mRequestLayoutContentPaneCalled);
+ assertEquals(2, a.mOnLayoutCalled);
+ assertEquals(2, b.mOnLayoutCalled);
+ assertEquals(1, c.mOnLayoutCalled);
+ }
+
+ @SmallTest
+ public void testRender() {
+ GLViewMock a = new GLViewMock();
+ GLViewMock b = new GLViewMock();
+
+ a.addComponent(b);
+ GLCanvasStub canvas = new GLCanvasStub();
+ assertEquals(0, a.mRenderBackgroundCalled);
+ assertEquals(0, b.mRenderBackgroundCalled);
+ a.render(canvas);
+ assertEquals(1, a.mRenderBackgroundCalled);
+ assertEquals(1, b.mRenderBackgroundCalled);
+ }
+
+ @SmallTest
+ public void testMeasure() {
+ GLViewMock a = new GLViewMock();
+ GLViewMock b = new GLViewMock();
+ GLViewMock c = new GLViewMock();
+ GLRootMock r = new GLRootMock();
+
+ a.addComponent(b);
+ a.addComponent(c);
+ a.attachToRoot(r);
+
+ assertEquals(0, a.mOnMeasureCalled);
+ a.measure(100, 200);
+ assertEquals(1, a.mOnMeasureCalled);
+ assertEquals(1, b.mOnMeasureCalled);
+ assertEquals(100, a.mOnMeasureWidthSpec);
+ assertEquals(200, a.mOnMeasureHeightSpec);
+ assertEquals(100, b.mOnMeasureWidthSpec);
+ assertEquals(200, b.mOnMeasureHeightSpec);
+ assertEquals(100, a.getMeasuredWidth());
+ assertEquals(200, b.getMeasuredHeight());
+
+ // same spec should not trigger onMeasure
+ a.measure(100, 200);
+ assertEquals(1, a.mOnMeasureCalled);
+
+ // unless someone requested it, but only those on the path
+ // to the requester.
+ b.requestLayout();
+ a.measure(100, 200);
+ assertEquals(2, a.mOnMeasureCalled);
+ assertEquals(2, b.mOnMeasureCalled);
+ assertEquals(1, c.mOnMeasureCalled);
+ }
+
+ class MyGLView extends GLView {
+ private int mWidth;
+ int mOnTouchCalled;
+ int mOnTouchX;
+ int mOnTouchY;
+ int mOnTouchAction;
+
+ public MyGLView(int width) {
+ mWidth = width;
+ }
+
+ @Override
+ protected void onLayout(boolean changeSize, int left, int top,
+ int right, int bottom) {
+ // layout children from left to right
+ // call children's layout.
+ int x = 0;
+ for (int i = 0, n = getComponentCount(); i < n; ++i) {
+ GLView item = getComponent(i);
+ item.measure(0, 0);
+ int w = item.getMeasuredWidth();
+ int h = item.getMeasuredHeight();
+ item.layout(x, 0, x + w, h);
+ x += w;
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthSpec, int heightSpec) {
+ setMeasuredSize(mWidth, 100);
+ }
+
+ @Override
+ protected boolean onTouch(MotionEvent event) {
+ mOnTouchCalled++;
+ mOnTouchX = (int) event.getX();
+ mOnTouchY = (int) event.getY();
+ mOnTouchAction = event.getAction();
+ return true;
+ }
+ }
+
+ private MotionEvent NewMotionEvent(int action, int x, int y) {
+ return MotionEvent.obtain(0, 0, action, x, y, 0);
+ }
+
+ @SmallTest
+ public void testTouchEvent() {
+ // We construct a tree with four nodes. Only the x coordinate is used:
+ // A = [0..............................300)
+ // B = [0......100)
+ // C = [100......200)
+ // D = [100..150)
+
+ MyGLView a = new MyGLView(300);
+ MyGLView b = new MyGLView(100);
+ MyGLView c = new MyGLView(100);
+ MyGLView d = new MyGLView(50);
+ GLRoot r = new GLRootStub();
+
+ a.addComponent(b);
+ a.addComponent(c);
+ c.addComponent(d);
+ a.attachToRoot(r);
+ a.layout(0, 0, 300, 100);
+
+ int DOWN = MotionEvent.ACTION_DOWN;
+ int UP = MotionEvent.ACTION_UP;
+ int MOVE = MotionEvent.ACTION_MOVE;
+ int CANCEL = MotionEvent.ACTION_CANCEL;
+
+ // simple case
+ assertEquals(0, a.mOnTouchCalled);
+ a.dispatchTouchEvent(NewMotionEvent(DOWN, 250, 0));
+ assertEquals(DOWN, a.mOnTouchAction);
+ a.dispatchTouchEvent(NewMotionEvent(UP, 250, 0));
+ assertEquals(UP, a.mOnTouchAction);
+ assertEquals(2, a.mOnTouchCalled);
+
+ // pass to a child, check the location is offseted.
+ assertEquals(0, c.mOnTouchCalled);
+ a.dispatchTouchEvent(NewMotionEvent(DOWN, 175, 0));
+ a.dispatchTouchEvent(NewMotionEvent(UP, 175, 0));
+ assertEquals(75, c.mOnTouchX);
+ assertEquals(0, c.mOnTouchY);
+ assertEquals(2, c.mOnTouchCalled);
+ assertEquals(2, a.mOnTouchCalled);
+
+ // motion target cancel event
+ assertEquals(0, d.mOnTouchCalled);
+ a.dispatchTouchEvent(NewMotionEvent(DOWN, 125, 0));
+ assertEquals(1, d.mOnTouchCalled);
+ a.dispatchTouchEvent(NewMotionEvent(MOVE, 250, 0));
+ assertEquals(2, d.mOnTouchCalled);
+ a.dispatchTouchEvent(NewMotionEvent(MOVE, 50, 0));
+ assertEquals(3, d.mOnTouchCalled);
+ a.dispatchTouchEvent(NewMotionEvent(DOWN, 175, 0));
+ assertEquals(4, d.mOnTouchCalled);
+ assertEquals(CANCEL, d.mOnTouchAction);
+ assertEquals(3, c.mOnTouchCalled);
+ assertEquals(DOWN, c.mOnTouchAction);
+ a.dispatchTouchEvent(NewMotionEvent(UP, 175, 0));
+
+ // motion target is removed
+ assertEquals(4, d.mOnTouchCalled);
+ a.dispatchTouchEvent(NewMotionEvent(DOWN, 125, 0));
+ assertEquals(5, d.mOnTouchCalled);
+ a.removeComponent(c);
+ assertEquals(6, d.mOnTouchCalled);
+ assertEquals(CANCEL, d.mOnTouchAction);
+
+ // invisible component should not get events
+ assertEquals(2, a.mOnTouchCalled);
+ assertEquals(0, b.mOnTouchCalled);
+ b.setVisibility(GLView.INVISIBLE);
+ a.dispatchTouchEvent(NewMotionEvent(DOWN, 50, 0));
+ assertEquals(3, a.mOnTouchCalled);
+ assertEquals(0, b.mOnTouchCalled);
+ }
+}
diff --git a/tests/src/com/android/gallery3d/ui/PointerInfo.java b/tests/src/com/android/gallery3d/ui/PointerInfo.java
new file mode 100644
index 0000000..6c78556
--- /dev/null
+++ b/tests/src/com/android/gallery3d/ui/PointerInfo.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2010 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.ui;
+
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.CharBuffer;
+import java.nio.DoubleBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.LongBuffer;
+import java.nio.ShortBuffer;
+
+import javax.microedition.khronos.opengles.GL10;
+
+public class PointerInfo {
+
+ /**
+ * The number of coordinates per vertex. 1..4
+ */
+ public int mSize;
+
+ /**
+ * The type of each coordinate.
+ */
+ public int mType;
+
+ /**
+ * The byte offset between consecutive vertices. 0 means mSize *
+ * sizeof(mType)
+ */
+ public int mStride;
+ public Buffer mPointer;
+ public ByteBuffer mTempByteBuffer;
+
+ public PointerInfo(int size, int type, int stride, Buffer pointer) {
+ mSize = size;
+ mType = type;
+ mStride = stride;
+ mPointer = pointer;
+ }
+
+ private int getStride() {
+ return mStride > 0 ? mStride : sizeof(mType) * mSize;
+ }
+
+ public void bindByteBuffer() {
+ mTempByteBuffer = mPointer == null ? null : toByteBuffer(-1, mPointer);
+ }
+
+ public void unbindByteBuffer() {
+ mTempByteBuffer = null;
+ }
+
+ private static int sizeof(int type) {
+ switch (type) {
+ case GL10.GL_UNSIGNED_BYTE:
+ return 1;
+ case GL10.GL_BYTE:
+ return 1;
+ case GL10.GL_SHORT:
+ return 2;
+ case GL10.GL_FIXED:
+ return 4;
+ case GL10.GL_FLOAT:
+ return 4;
+ default:
+ return 0;
+ }
+ }
+
+ private static ByteBuffer toByteBuffer(int byteCount, Buffer input) {
+ ByteBuffer result = null;
+ boolean convertWholeBuffer = (byteCount < 0);
+ if (input instanceof ByteBuffer) {
+ ByteBuffer input2 = (ByteBuffer) input;
+ int position = input2.position();
+ if (convertWholeBuffer) {
+ byteCount = input2.limit() - position;
+ }
+ result = ByteBuffer.allocate(byteCount).order(input2.order());
+ for (int i = 0; i < byteCount; i++) {
+ result.put(input2.get());
+ }
+ input2.position(position);
+ } else if (input instanceof CharBuffer) {
+ CharBuffer input2 = (CharBuffer) input;
+ int position = input2.position();
+ if (convertWholeBuffer) {
+ byteCount = (input2.limit() - position) * 2;
+ }
+ result = ByteBuffer.allocate(byteCount).order(input2.order());
+ CharBuffer result2 = result.asCharBuffer();
+ for (int i = 0; i < byteCount / 2; i++) {
+ result2.put(input2.get());
+ }
+ input2.position(position);
+ } else if (input instanceof ShortBuffer) {
+ ShortBuffer input2 = (ShortBuffer) input;
+ int position = input2.position();
+ if (convertWholeBuffer) {
+ byteCount = (input2.limit() - position)* 2;
+ }
+ result = ByteBuffer.allocate(byteCount).order(input2.order());
+ ShortBuffer result2 = result.asShortBuffer();
+ for (int i = 0; i < byteCount / 2; i++) {
+ result2.put(input2.get());
+ }
+ input2.position(position);
+ } else if (input instanceof IntBuffer) {
+ IntBuffer input2 = (IntBuffer) input;
+ int position = input2.position();
+ if (convertWholeBuffer) {
+ byteCount = (input2.limit() - position) * 4;
+ }
+ result = ByteBuffer.allocate(byteCount).order(input2.order());
+ IntBuffer result2 = result.asIntBuffer();
+ for (int i = 0; i < byteCount / 4; i++) {
+ result2.put(input2.get());
+ }
+ input2.position(position);
+ } else if (input instanceof FloatBuffer) {
+ FloatBuffer input2 = (FloatBuffer) input;
+ int position = input2.position();
+ if (convertWholeBuffer) {
+ byteCount = (input2.limit() - position) * 4;
+ }
+ result = ByteBuffer.allocate(byteCount).order(input2.order());
+ FloatBuffer result2 = result.asFloatBuffer();
+ for (int i = 0; i < byteCount / 4; i++) {
+ result2.put(input2.get());
+ }
+ input2.position(position);
+ } else if (input instanceof DoubleBuffer) {
+ DoubleBuffer input2 = (DoubleBuffer) input;
+ int position = input2.position();
+ if (convertWholeBuffer) {
+ byteCount = (input2.limit() - position) * 8;
+ }
+ result = ByteBuffer.allocate(byteCount).order(input2.order());
+ DoubleBuffer result2 = result.asDoubleBuffer();
+ for (int i = 0; i < byteCount / 8; i++) {
+ result2.put(input2.get());
+ }
+ input2.position(position);
+ } else if (input instanceof LongBuffer) {
+ LongBuffer input2 = (LongBuffer) input;
+ int position = input2.position();
+ if (convertWholeBuffer) {
+ byteCount = (input2.limit() - position) * 8;
+ }
+ result = ByteBuffer.allocate(byteCount).order(input2.order());
+ LongBuffer result2 = result.asLongBuffer();
+ for (int i = 0; i < byteCount / 8; i++) {
+ result2.put(input2.get());
+ }
+ input2.position(position);
+ } else {
+ throw new RuntimeException("Unimplemented Buffer subclass.");
+ }
+ result.rewind();
+ // The OpenGL API will interpret the result in hardware byte order,
+ // so we better do that as well:
+ result.order(ByteOrder.nativeOrder());
+ return result;
+ }
+
+ public void getArrayElement(int index, double[] result) {
+ if (mTempByteBuffer == null) {
+ throw new IllegalArgumentException("undefined pointer");
+ }
+ if (mStride < 0) {
+ throw new IllegalArgumentException("invalid stride");
+ }
+
+ int stride = getStride();
+ ByteBuffer byteBuffer = mTempByteBuffer;
+ int size = mSize;
+ int type = mType;
+ int sizeofType = sizeof(type);
+ int byteOffset = stride * index;
+
+ for (int i = 0; i < size; i++) {
+ switch (type) {
+ case GL10.GL_BYTE:
+ case GL10.GL_UNSIGNED_BYTE:
+ result[i] = byteBuffer.get(byteOffset);
+ break;
+ case GL10.GL_SHORT:
+ ShortBuffer shortBuffer = byteBuffer.asShortBuffer();
+ result[i] = shortBuffer.get(byteOffset / 2);
+ break;
+ case GL10.GL_FIXED:
+ IntBuffer intBuffer = byteBuffer.asIntBuffer();
+ result[i] = intBuffer.get(byteOffset / 4);
+ break;
+ case GL10.GL_FLOAT:
+ FloatBuffer floatBuffer = byteBuffer.asFloatBuffer();
+ result[i] = floatBuffer.get(byteOffset / 4);
+ break;
+ default:
+ throw new UnsupportedOperationException("unknown type");
+ }
+ byteOffset += sizeofType;
+ }
+ }
+}
diff --git a/tests/src/com/android/gallery3d/unittest/CameraUnitTest.java b/tests/src/com/android/gallery3d/unittest/CameraUnitTest.java
new file mode 100644
index 0000000..b8fb05f
--- /dev/null
+++ b/tests/src/com/android/gallery3d/unittest/CameraUnitTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2010 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.unittest;
+
+import com.android.camera.Util;
+
+import android.graphics.Matrix;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+@SmallTest
+public class CameraUnitTest extends TestCase {
+ public void testRoundOrientation() {
+ int h = Util.ORIENTATION_HYSTERESIS;
+ assertEquals(0, Util.roundOrientation(0, 0));
+ assertEquals(0, Util.roundOrientation(359, 0));
+ assertEquals(0, Util.roundOrientation(0 + 44 + h, 0));
+ assertEquals(90, Util.roundOrientation(0 + 45 + h, 0));
+ assertEquals(0, Util.roundOrientation(360 - 44 - h, 0));
+ assertEquals(270, Util.roundOrientation(360 - 45 - h, 0));
+
+ assertEquals(90, Util.roundOrientation(90, 90));
+ assertEquals(90, Util.roundOrientation(90 + 44 + h, 90));
+ assertEquals(180, Util.roundOrientation(90 + 45 + h, 90));
+ assertEquals(90, Util.roundOrientation(90 - 44 - h, 90));
+ assertEquals(0, Util.roundOrientation(90 - 45 - h, 90));
+
+ assertEquals(180, Util.roundOrientation(180, 180));
+ assertEquals(180, Util.roundOrientation(180 + 44 + h, 180));
+ assertEquals(270, Util.roundOrientation(180 + 45 + h, 180));
+ assertEquals(180, Util.roundOrientation(180 - 44 - h, 180));
+ assertEquals(90, Util.roundOrientation(180 - 45 - h, 180));
+
+ assertEquals(270, Util.roundOrientation(270, 270));
+ assertEquals(270, Util.roundOrientation(270 + 44 + h, 270));
+ assertEquals(0, Util.roundOrientation(270 + 45 + h, 270));
+ assertEquals(270, Util.roundOrientation(270 - 44 - h, 270));
+ assertEquals(180, Util.roundOrientation(270 - 45 - h, 270));
+
+ assertEquals(90, Util.roundOrientation(90, 0));
+ assertEquals(180, Util.roundOrientation(180, 0));
+ assertEquals(270, Util.roundOrientation(270, 0));
+
+ assertEquals(0, Util.roundOrientation(0, 90));
+ assertEquals(180, Util.roundOrientation(180, 90));
+ assertEquals(270, Util.roundOrientation(270, 90));
+
+ assertEquals(0, Util.roundOrientation(0, 180));
+ assertEquals(90, Util.roundOrientation(90, 180));
+ assertEquals(270, Util.roundOrientation(270, 180));
+
+ assertEquals(0, Util.roundOrientation(0, 270));
+ assertEquals(90, Util.roundOrientation(90, 270));
+ assertEquals(180, Util.roundOrientation(180, 270));
+ }
+
+ public void testPrepareMatrix() {
+ Matrix matrix = new Matrix();
+ float[] points;
+ int[] expected;
+
+ Util.prepareMatrix(matrix, false, 0, 800, 480);
+ points = new float[] {-1000, -1000, 0, 0, 1000, 1000, 0, 1000, -750, 250};
+ expected = new int[] {0, 0, 400, 240, 800, 480, 400, 480, 100, 300};
+ matrix.mapPoints(points);
+ assertEquals(expected, points);
+
+ Util.prepareMatrix(matrix, false, 90, 800, 480);
+ points = new float[] {-1000, -1000, 0, 0, 1000, 1000, 0, 1000, -750, 250};
+ expected = new int[] {800, 0, 400, 240, 0, 480, 0, 240, 300, 60};
+ matrix.mapPoints(points);
+ assertEquals(expected, points);
+
+ Util.prepareMatrix(matrix, false, 180, 800, 480);
+ points = new float[] {-1000, -1000, 0, 0, 1000, 1000, 0, 1000, -750, 250};
+ expected = new int[] {800, 480, 400, 240, 0, 0, 400, 0, 700, 180};
+ matrix.mapPoints(points);
+ assertEquals(expected, points);
+
+ Util.prepareMatrix(matrix, true, 180, 800, 480);
+ points = new float[] {-1000, -1000, 0, 0, 1000, 1000, 0, 1000, -750, 250};
+ expected = new int[] {0, 480, 400, 240, 800, 0, 400, 0, 100, 180};
+ matrix.mapPoints(points);
+ assertEquals(expected, points);
+ }
+
+ private void assertEquals(int expected[], float[] actual) {
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals("Array index " + i + " mismatch", expected[i], Math.round(actual[i]));
+ }
+ }
+}
diff --git a/tests/src/com/android/gallery3d/util/IntArrayTest.java b/tests/src/com/android/gallery3d/util/IntArrayTest.java
new file mode 100644
index 0000000..83e6050
--- /dev/null
+++ b/tests/src/com/android/gallery3d/util/IntArrayTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2010 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.util;
+
+import com.android.gallery3d.util.IntArray;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import java.util.Arrays;
+import junit.framework.TestCase;
+
+@SmallTest
+public class IntArrayTest extends TestCase {
+ private static final String TAG = "IntArrayTest";
+
+ public void testIntArray() {
+ IntArray a = new IntArray();
+ assertEquals(0, a.size());
+ assertTrue(Arrays.equals(new int[] {}, a.toArray(null)));
+
+ a.add(0);
+ assertEquals(1, a.size());
+ assertTrue(Arrays.equals(new int[] {0}, a.toArray(null)));
+
+ a.add(1);
+ assertEquals(2, a.size());
+ assertTrue(Arrays.equals(new int[] {0, 1}, a.toArray(null)));
+
+ int[] buf = new int[2];
+ int[] result = a.toArray(buf);
+ assertSame(buf, result);
+
+ IntArray b = new IntArray();
+ for (int i = 0; i < 100; i++) {
+ b.add(i * i);
+ }
+
+ assertEquals(100, b.size());
+ result = b.toArray(buf);
+ assertEquals(100, result.length);
+ for (int i = 0; i < 100; i++) {
+ assertEquals(i * i, result[i]);
+ }
+ }
+}
diff --git a/tests/src/com/android/gallery3d/util/ProfileTest.java b/tests/src/com/android/gallery3d/util/ProfileTest.java
new file mode 100644
index 0000000..798b905
--- /dev/null
+++ b/tests/src/com/android/gallery3d/util/ProfileTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.util;
+
+import com.android.gallery3d.util.Profile;
+
+import android.os.Environment;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+@SmallTest
+public class ProfileTest extends TestCase {
+ private static final String TAG = "ProfileTest";
+ private static final String TEST_FILE =
+ Environment.getExternalStorageDirectory().getPath() + "/test.dat";
+
+
+ public void testProfile() throws IOException {
+ ProfileData p = new ProfileData();
+ ParsedProfile q;
+ String[] A = {"A"};
+ String[] B = {"B"};
+ String[] AC = {"A", "C"};
+ String[] AD = {"A", "D"};
+
+ // Empty profile
+ p.dumpToFile(TEST_FILE);
+ q = new ParsedProfile(TEST_FILE);
+ assertTrue(q.mEntries.isEmpty());
+ assertTrue(q.mSymbols.isEmpty());
+
+ // Only one sample
+ p.addSample(A);
+ p.dumpToFile(TEST_FILE);
+ q = new ParsedProfile(TEST_FILE);
+ assertEquals(1, q.mEntries.size());
+ assertEquals(1, q.mSymbols.size());
+ assertEquals(1, q.mEntries.get(0).sampleCount);
+
+ // Two samples at the same place
+ p.addSample(A);
+ p.dumpToFile(TEST_FILE);
+ q = new ParsedProfile(TEST_FILE);
+ assertEquals(1, q.mEntries.size());
+ assertEquals(1, q.mSymbols.size());
+ assertEquals(2, q.mEntries.get(0).sampleCount);
+
+ // Two samples at the different places
+ p.reset();
+ p.addSample(A);
+ p.addSample(B);
+ p.dumpToFile(TEST_FILE);
+ q = new ParsedProfile(TEST_FILE);
+ assertEquals(2, q.mEntries.size());
+ assertEquals(2, q.mSymbols.size());
+ assertEquals(1, q.mEntries.get(0).sampleCount);
+ assertEquals(1, q.mEntries.get(1).sampleCount);
+
+ // depth > 1
+ p.reset();
+ p.addSample(AC);
+ p.dumpToFile(TEST_FILE);
+ q = new ParsedProfile(TEST_FILE);
+ assertEquals(1, q.mEntries.size());
+ assertEquals(2, q.mSymbols.size());
+ assertEquals(1, q.mEntries.get(0).sampleCount);
+
+ // two samples (AC and AD)
+ p.addSample(AD);
+ p.dumpToFile(TEST_FILE);
+ q = new ParsedProfile(TEST_FILE);
+ assertEquals(2, q.mEntries.size());
+ assertEquals(3, q.mSymbols.size()); // three symbols: A, C, D
+ assertEquals(1, q.mEntries.get(0).sampleCount);
+ assertEquals(1, q.mEntries.get(0).sampleCount);
+
+ // Remove the test file
+ new File(TEST_FILE).delete();
+ }
+}
+
+class ParsedProfile {
+ public class Entry {
+ int sampleCount;
+ int stackId[];
+ }
+
+ ArrayList<Entry> mEntries = new ArrayList<Entry>();
+ HashMap<Integer, String> mSymbols = new HashMap<Integer, String>();
+ private DataInputStream mIn;
+ private byte[] mScratch = new byte[4]; // scratch buffer for readInt
+
+ public ParsedProfile(String filename) throws IOException {
+ mIn = new DataInputStream(new FileInputStream(filename));
+
+ Entry entry = parseOneEntry();
+ checkIsFirstEntry(entry);
+
+ while (true) {
+ entry = parseOneEntry();
+ if (entry.sampleCount == 0) {
+ checkIsLastEntry(entry);
+ break;
+ }
+ mEntries.add(entry);
+ }
+
+ // Read symbol table
+ while (true) {
+ String line = mIn.readLine();
+ if (line == null) break;
+ String[] fields = line.split(" +");
+ checkIsValidSymbolLine(fields);
+ mSymbols.put(Integer.decode(fields[0]), fields[1]);
+ }
+ }
+
+ private void checkIsFirstEntry(Entry entry) {
+ Assert.assertEquals(0, entry.sampleCount);
+ Assert.assertEquals(3, entry.stackId.length);
+ Assert.assertEquals(1, entry.stackId[0]);
+ Assert.assertTrue(entry.stackId[1] > 0); // sampling period
+ Assert.assertEquals(0, entry.stackId[2]); // padding
+ }
+
+ private void checkIsLastEntry(Entry entry) {
+ Assert.assertEquals(0, entry.sampleCount);
+ Assert.assertEquals(1, entry.stackId.length);
+ Assert.assertEquals(0, entry.stackId[0]);
+ }
+
+ private void checkIsValidSymbolLine(String[] fields) {
+ Assert.assertEquals(2, fields.length);
+ Assert.assertTrue(fields[0].startsWith("0x"));
+ }
+
+ private Entry parseOneEntry() throws IOException {
+ int sampleCount = readInt();
+ int depth = readInt();
+ Entry e = new Entry();
+ e.sampleCount = sampleCount;
+ e.stackId = new int[depth];
+ for (int i = 0; i < depth; i++) {
+ e.stackId[i] = readInt();
+ }
+ return e;
+ }
+
+ private int readInt() throws IOException {
+ mIn.read(mScratch, 0, 4);
+ return (mScratch[0] & 0xff) |
+ ((mScratch[1] & 0xff) << 8) |
+ ((mScratch[2] & 0xff) << 16) |
+ ((mScratch[3] & 0xff) << 24);
+ }
+}
diff --git a/tests/src/com/android/photos/data/DataTestRunner.java b/tests/src/com/android/photos/data/DataTestRunner.java
new file mode 100644
index 0000000..10618d6
--- /dev/null
+++ b/tests/src/com/android/photos/data/DataTestRunner.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2013 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.photos.data;
+
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+
+import com.android.photos.data.TestHelper.TestInitialization;
+
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+public class DataTestRunner extends InstrumentationTestRunner {
+ @Override
+ public TestSuite getAllTests() {
+ TestSuite suite = new InstrumentationTestSuite(this);
+ suite.addTestSuite(PhotoDatabaseTest.class);
+ suite.addTestSuite(PhotoProviderTest.class);
+ TestHelper.addTests(MediaCacheTest.class, suite, new TestInitialization() {
+ @Override
+ public void initialize(TestCase testCase) {
+ MediaCacheTest test = (MediaCacheTest) testCase;
+ test.setLocalContext(getContext());
+ }
+ });
+ return suite;
+ }
+
+ @Override
+ public ClassLoader getLoader() {
+ return DataTestRunner.class.getClassLoader();
+ }
+}
diff --git a/tests/src/com/android/photos/data/MediaCacheTest.java b/tests/src/com/android/photos/data/MediaCacheTest.java
new file mode 100644
index 0000000..9e71128
--- /dev/null
+++ b/tests/src/com/android/photos/data/MediaCacheTest.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2013 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.photos.data;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.SystemClock;
+import android.test.ProviderTestCase2;
+
+import com.android.gallery3d.tests.R;
+import com.android.photos.data.MediaCache.ImageReady;
+import com.android.photos.data.MediaCache.OriginalReady;
+import com.android.photos.data.MediaRetriever.MediaSize;
+import com.android.photos.data.PhotoProvider.Photos;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class MediaCacheTest extends ProviderTestCase2<PhotoProvider> {
+ @SuppressWarnings("unused")
+ private static final String TAG = MediaCacheTest.class.getSimpleName();
+
+ private File mDir;
+ private File mImage;
+ private File mCacheDir;
+ private Resources mResources;
+ private MediaCache mMediaCache;
+ private ReadyCollector mReady;
+
+ public static final long MAX_WAIT = 2000;
+
+ private static class ReadyCollector implements ImageReady, OriginalReady {
+ public File mOriginalFile;
+ public InputStream mInputStream;
+
+ @Override
+ public synchronized void originalReady(File originalFile) {
+ mOriginalFile = originalFile;
+ notifyAll();
+ }
+
+ @Override
+ public synchronized void imageReady(InputStream bitmapInputStream) {
+ mInputStream = bitmapInputStream;
+ notifyAll();
+ }
+
+ public synchronized boolean waitForNotification() {
+ long endWait = SystemClock.uptimeMillis() + MAX_WAIT;
+
+ try {
+ while (mInputStream == null && mOriginalFile == null
+ && SystemClock.uptimeMillis() < endWait) {
+ wait(endWait - SystemClock.uptimeMillis());
+ }
+ } catch (InterruptedException e) {
+ }
+ return mInputStream != null || mOriginalFile != null;
+ }
+ }
+
+ private static class DummyMediaRetriever implements MediaRetriever {
+ private boolean mNullUri = false;
+ @Override
+ public File getLocalFile(Uri contentUri) {
+ return null;
+ }
+
+ @Override
+ public MediaSize getFastImageSize(Uri contentUri, MediaSize size) {
+ return null;
+ }
+
+ @Override
+ public byte[] getTemporaryImage(Uri contentUri, MediaSize temporarySize) {
+ return null;
+ }
+
+ @Override
+ public boolean getMedia(Uri contentUri, MediaSize imageSize, File tempFile) {
+ return false;
+ }
+
+ @Override
+ public Uri normalizeUri(Uri contentUri, MediaSize size) {
+ if (mNullUri) {
+ return null;
+ } else {
+ return contentUri;
+ }
+ }
+
+ @Override
+ public MediaSize normalizeMediaSize(Uri contentUri, MediaSize size) {
+ return size;
+ }
+
+ public void setNullUri() {
+ mNullUri = true;
+ }
+ };
+
+ public MediaCacheTest() {
+ super(PhotoProvider.class, PhotoProvider.AUTHORITY);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mReady = new ReadyCollector();
+ File externalDir = Environment.getExternalStorageDirectory();
+ mDir = new File(externalDir, "test");
+ mDir.mkdirs();
+ mCacheDir = new File(externalDir, "test_cache");
+ mImage = new File(mDir, "original.jpg");
+ MediaCache.initialize(getMockContext());
+ MediaCache.getInstance().setCacheDir(mCacheDir);
+ mMediaCache = MediaCache.getInstance();
+ mMediaCache.addRetriever("file", "", new FileRetriever());
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ mMediaCache.clearCacheDir();
+ MediaCache.shutdown();
+ mMediaCache = null;
+ mImage.delete();
+ mDir.delete();
+ mCacheDir.delete();
+ }
+
+ public void setLocalContext(Context context) {
+ mResources = context.getResources();
+ }
+
+ public void testRetrieveOriginal() throws IOException {
+ copyResourceToFile(R.raw.galaxy_nexus, mImage.getPath());
+ Uri uri = Uri.fromFile(mImage);
+ mMediaCache.retrieveOriginal(uri, mReady, null);
+ assertTrue(mReady.waitForNotification());
+ assertNull(mReady.mInputStream);
+ assertEquals(mImage, mReady.mOriginalFile);
+ }
+
+ public void testRetrievePreview() throws IOException {
+ copyResourceToFile(R.raw.galaxy_nexus, mImage.getPath());
+ Uri uri = Uri.fromFile(mImage);
+ mMediaCache.retrievePreview(uri, mReady, null);
+ assertTrue(mReady.waitForNotification());
+ assertNotNull(mReady.mInputStream);
+ assertNull(mReady.mOriginalFile);
+ Bitmap bitmap = BitmapFactory.decodeStream(mReady.mInputStream);
+ mReady.mInputStream.close();
+ assertNotNull(bitmap);
+ Bitmap original = BitmapFactory.decodeFile(mImage.getPath());
+ assertTrue(bitmap.getWidth() < original.getWidth());
+ assertTrue(bitmap.getHeight() < original.getHeight());
+ int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight());
+ int targetSize = MediaCacheUtils.getTargetSize(MediaSize.Preview);
+ assertTrue(maxDimension >= targetSize);
+ assertTrue(maxDimension < (targetSize * 2));
+ }
+
+ public void testRetrieveExifThumb() throws IOException {
+ copyResourceToFile(R.raw.galaxy_nexus, mImage.getPath());
+ Uri uri = Uri.fromFile(mImage);
+ ReadyCollector done = new ReadyCollector();
+ mMediaCache.retrieveThumbnail(uri, done, mReady);
+ assertTrue(mReady.waitForNotification());
+ assertNotNull(mReady.mInputStream);
+ assertNull(mReady.mOriginalFile);
+ Bitmap bitmap = BitmapFactory.decodeStream(mReady.mInputStream);
+ mReady.mInputStream.close();
+ assertTrue(done.waitForNotification());
+ assertNotNull(done.mInputStream);
+ done.mInputStream.close();
+ assertNotNull(bitmap);
+ assertEquals(320, bitmap.getWidth());
+ assertEquals(240, bitmap.getHeight());
+ }
+
+ public void testRetrieveThumb() throws IOException {
+ copyResourceToFile(R.raw.galaxy_nexus, mImage.getPath());
+ Uri uri = Uri.fromFile(mImage);
+ long downsampleStart = SystemClock.uptimeMillis();
+ mMediaCache.retrieveThumbnail(uri, mReady, null);
+ assertTrue(mReady.waitForNotification());
+ long downsampleEnd = SystemClock.uptimeMillis();
+ assertNotNull(mReady.mInputStream);
+ assertNull(mReady.mOriginalFile);
+ Bitmap bitmap = BitmapFactory.decodeStream(mReady.mInputStream);
+ mReady.mInputStream.close();
+ assertNotNull(bitmap);
+ Bitmap original = BitmapFactory.decodeFile(mImage.getPath());
+ assertTrue(bitmap.getWidth() < original.getWidth());
+ assertTrue(bitmap.getHeight() < original.getHeight());
+ int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight());
+ int targetSize = MediaCacheUtils.getTargetSize(MediaSize.Thumbnail);
+ assertTrue(maxDimension >= targetSize);
+ assertTrue(maxDimension < (targetSize * 2));
+
+ // Retrieve cached thumb.
+ mReady = new ReadyCollector();
+ long start = SystemClock.uptimeMillis();
+ mMediaCache.retrieveThumbnail(uri, mReady, null);
+ assertTrue(mReady.waitForNotification());
+ mReady.mInputStream.close();
+ long end = SystemClock.uptimeMillis();
+ // Already cached. Wait shorter time.
+ assertTrue((end - start) < (downsampleEnd - downsampleStart) / 2);
+ }
+
+ public void testGetVideo() throws IOException {
+ mImage = new File(mDir, "original.mp4");
+ copyResourceToFile(R.raw.android_lawn, mImage.getPath());
+ Uri uri = Uri.fromFile(mImage);
+
+ mMediaCache.retrieveOriginal(uri, mReady, null);
+ assertTrue(mReady.waitForNotification());
+ assertNull(mReady.mInputStream);
+ assertNotNull(mReady.mOriginalFile);
+
+ mReady = new ReadyCollector();
+ mMediaCache.retrievePreview(uri, mReady, null);
+ assertTrue(mReady.waitForNotification());
+ assertNotNull(mReady.mInputStream);
+ assertNull(mReady.mOriginalFile);
+ Bitmap bitmap = BitmapFactory.decodeStream(mReady.mInputStream);
+ mReady.mInputStream.close();
+ int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight());
+ int targetSize = MediaCacheUtils.getTargetSize(MediaSize.Preview);
+ assertTrue(maxDimension >= targetSize);
+ assertTrue(maxDimension < (targetSize * 2));
+
+ mReady = new ReadyCollector();
+ mMediaCache.retrieveThumbnail(uri, mReady, null);
+ assertTrue(mReady.waitForNotification());
+ assertNotNull(mReady.mInputStream);
+ assertNull(mReady.mOriginalFile);
+ bitmap = BitmapFactory.decodeStream(mReady.mInputStream);
+ mReady.mInputStream.close();
+ maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight());
+ targetSize = MediaCacheUtils.getTargetSize(MediaSize.Thumbnail);
+ assertTrue(maxDimension >= targetSize);
+ assertTrue(maxDimension < (targetSize * 2));
+ }
+
+ public void testFastImage() throws IOException {
+ copyResourceToFile(R.raw.galaxy_nexus, mImage.getPath());
+ Uri uri = Uri.fromFile(mImage);
+ mMediaCache.retrieveThumbnail(uri, mReady, null);
+ mReady.waitForNotification();
+ mReady.mInputStream.close();
+
+ mMediaCache.retrieveOriginal(uri, mReady, null);
+ assertTrue(mReady.waitForNotification());
+ assertNotNull(mReady.mInputStream);
+ mReady.mInputStream.close();
+ }
+
+ public void testBadRetriever() {
+ Uri uri = Photos.CONTENT_URI;
+ try {
+ mMediaCache.retrieveOriginal(uri, mReady, null);
+ fail("Expected exception");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ public void testInsertIntoCache() throws IOException {
+ // FileRetriever inserts into the cache opportunistically with Videos
+ mImage = new File(mDir, "original.mp4");
+ copyResourceToFile(R.raw.android_lawn, mImage.getPath());
+ Uri uri = Uri.fromFile(mImage);
+
+ mMediaCache.retrieveThumbnail(uri, mReady, null);
+ assertTrue(mReady.waitForNotification());
+ mReady.mInputStream.close();
+ assertNotNull(mMediaCache.getCachedFile(uri, MediaSize.Preview));
+ }
+
+ public void testBadNormalizedUri() {
+ DummyMediaRetriever retriever = new DummyMediaRetriever();
+ Uri uri = Uri.fromParts("http", "world", "morestuff");
+ mMediaCache.addRetriever(uri.getScheme(), uri.getAuthority(), retriever);
+ retriever.setNullUri();
+ try {
+ mMediaCache.retrieveOriginal(uri, mReady, null);
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ public void testClearOldCache() throws IOException {
+ copyResourceToFile(R.raw.galaxy_nexus, mImage.getPath());
+ Uri uri = Uri.fromFile(mImage);
+ mMediaCache.retrievePreview(uri, mReady, null);
+ assertTrue(mReady.waitForNotification());
+ mReady.mInputStream.close();
+ mMediaCache.setMaxCacheSize(mMediaCache.getCachedFile(uri, MediaSize.Preview).length());
+ assertNotNull(mMediaCache.getCachedFile(uri, MediaSize.Preview));
+
+ mReady = new ReadyCollector();
+ // This should kick the preview image out of the cache.
+ mMediaCache.retrieveThumbnail(uri, mReady, null);
+ assertTrue(mReady.waitForNotification());
+ mReady.mInputStream.close();
+ assertNull(mMediaCache.getCachedFile(uri, MediaSize.Preview));
+ assertNotNull(mMediaCache.getCachedFile(uri, MediaSize.Thumbnail));
+ }
+
+ public void testClearLargeInCache() throws IOException {
+ copyResourceToFile(R.raw.galaxy_nexus, mImage.getPath());
+ Uri imageUri = Uri.fromFile(mImage);
+ mMediaCache.retrieveThumbnail(imageUri, mReady, null);
+ assertTrue(mReady.waitForNotification());
+ mReady.mInputStream.close();
+ assertNotNull(mMediaCache.getCachedFile(imageUri, MediaSize.Thumbnail));
+ long thumbSize = mMediaCache.getCachedFile(imageUri, MediaSize.Thumbnail).length();
+ mMediaCache.setMaxCacheSize(thumbSize * 10);
+
+ for (int i = 0; i < 9; i++) {
+ File tempImage = new File(mDir, "image" + i + ".jpg");
+ mImage.renameTo(tempImage);
+ Uri tempImageUri = Uri.fromFile(tempImage);
+ mReady = new ReadyCollector();
+ mMediaCache.retrieveThumbnail(tempImageUri, mReady, null);
+ assertTrue(mReady.waitForNotification());
+ mReady.mInputStream.close();
+ tempImage.renameTo(mImage);
+ }
+ assertNotNull(mMediaCache.getCachedFile(imageUri, MediaSize.Thumbnail));
+
+ for (int i = 0; i < 9; i++) {
+ File tempImage = new File(mDir, "image" + i + ".jpg");
+ mImage.renameTo(tempImage);
+ Uri tempImageUri = Uri.fromFile(tempImage);
+ mReady = new ReadyCollector();
+ mMediaCache.retrievePreview(tempImageUri, mReady, null);
+ assertTrue(mReady.waitForNotification());
+ mReady.mInputStream.close();
+ tempImage.renameTo(mImage);
+ }
+ assertNotNull(mMediaCache.getCachedFile(imageUri, MediaSize.Thumbnail));
+ Uri oldestUri = Uri.fromFile(new File(mDir, "image0.jpg"));
+ assertNull(mMediaCache.getCachedFile(oldestUri, MediaSize.Thumbnail));
+ }
+
+ private void copyResourceToFile(int resourceId, String path) throws IOException {
+ File outputDir = new File(path).getParentFile();
+ outputDir.mkdirs();
+
+ InputStream in = mResources.openRawResource(resourceId);
+ FileOutputStream out = new FileOutputStream(path);
+ byte[] buffer = new byte[1000];
+ int bytesRead;
+
+ while ((bytesRead = in.read(buffer)) >= 0) {
+ out.write(buffer, 0, bytesRead);
+ }
+
+ in.close();
+ out.close();
+ }
+}
diff --git a/tests/src/com/android/photos/data/PhotoDatabaseTest.java b/tests/src/com/android/photos/data/PhotoDatabaseTest.java
new file mode 100644
index 0000000..e7c1689
--- /dev/null
+++ b/tests/src/com/android/photos/data/PhotoDatabaseTest.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2013 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.photos.data;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.test.InstrumentationTestCase;
+
+import com.android.photos.data.PhotoProvider.Accounts;
+import com.android.photos.data.PhotoProvider.Albums;
+import com.android.photos.data.PhotoProvider.Metadata;
+import com.android.photos.data.PhotoProvider.Photos;
+
+import java.io.File;
+import java.io.IOException;
+
+public class PhotoDatabaseTest extends InstrumentationTestCase {
+
+ private PhotoDatabase mDBHelper;
+ private static final String DB_NAME = "dummy.db";
+ private static final long PARENT_ID1 = 100;
+ private static final long PARENT_ID2 = 101;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ Context context = getInstrumentation().getTargetContext();
+ context.deleteDatabase(DB_NAME);
+ mDBHelper = new PhotoDatabase(context, DB_NAME);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mDBHelper.close();
+ mDBHelper = null;
+ Context context = getInstrumentation().getTargetContext();
+ context.deleteDatabase(DB_NAME);
+ super.tearDown();
+ }
+
+ public void testCreateDatabase() throws IOException {
+ Context context = getInstrumentation().getTargetContext();
+ File dbFile = context.getDatabasePath(DB_NAME);
+ SQLiteDatabase db = getReadableDB();
+ db.beginTransaction();
+ db.endTransaction();
+ assertTrue(dbFile.exists());
+ }
+
+ public void testTables() {
+ validateTable(Metadata.TABLE, PhotoDatabaseUtils.PROJECTION_METADATA);
+ validateTable(Albums.TABLE, PhotoDatabaseUtils.PROJECTION_ALBUMS);
+ validateTable(Photos.TABLE, PhotoDatabaseUtils.PROJECTION_PHOTOS);
+ }
+
+ public void testAlbumsConstraints() {
+ SQLiteDatabase db = getWritableDB();
+ db.beginTransaction();
+ try {
+ long accountId = 100;
+ // Test NOT NULL constraint on name
+ assertFalse(PhotoDatabaseUtils.insertAlbum(db, null, null, Albums.VISIBILITY_PRIVATE,
+ accountId));
+
+ // test NOT NULL constraint on privacy
+ assertFalse(PhotoDatabaseUtils.insertAlbum(db, null, "hello", null, accountId));
+
+ // test NOT NULL constraint on account_id
+ assertFalse(PhotoDatabaseUtils.insertAlbum(db, null, "hello",
+ Albums.VISIBILITY_PRIVATE, null));
+
+ // Normal insert
+ assertTrue(PhotoDatabaseUtils.insertAlbum(db, PARENT_ID1, "hello",
+ Albums.VISIBILITY_PRIVATE, accountId));
+
+ long albumId = PhotoDatabaseUtils.queryAlbumIdFromParentId(db, PARENT_ID1);
+
+ // Assign a valid child
+ assertTrue(PhotoDatabaseUtils.insertAlbum(db, PARENT_ID2, "hello",
+ Albums.VISIBILITY_PRIVATE, accountId));
+
+ long otherAlbumId = PhotoDatabaseUtils.queryAlbumIdFromParentId(db, PARENT_ID2);
+ assertNotSame(albumId, otherAlbumId);
+
+ // This is a valid child of another album.
+ assertTrue(PhotoDatabaseUtils.insertAlbum(db, otherAlbumId, "hello",
+ Albums.VISIBILITY_PRIVATE, accountId));
+
+ // This isn't allowed due to uniqueness constraint (parent_id/name)
+ assertFalse(PhotoDatabaseUtils.insertAlbum(db, otherAlbumId, "hello",
+ Albums.VISIBILITY_PRIVATE, accountId));
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ public void testPhotosConstraints() {
+ SQLiteDatabase db = getWritableDB();
+ db.beginTransaction();
+ try {
+ int width = 100;
+ int height = 100;
+ long dateTaken = System.currentTimeMillis();
+ String mimeType = "test/test";
+ long accountId = 100;
+
+ // Test NOT NULL mime-type
+ assertFalse(PhotoDatabaseUtils.insertPhoto(db, width, height, dateTaken, null, null,
+ accountId));
+
+ // Test NOT NULL width
+ assertFalse(PhotoDatabaseUtils.insertPhoto(db, null, height, dateTaken, null, mimeType,
+ accountId));
+
+ // Test NOT NULL height
+ assertFalse(PhotoDatabaseUtils.insertPhoto(db, width, null, dateTaken, null, mimeType,
+ accountId));
+
+ // Test NOT NULL dateTaken
+ assertFalse(PhotoDatabaseUtils.insertPhoto(db, width, height, null, null, mimeType,
+ accountId));
+
+ // Test NOT NULL accountId
+ assertFalse(PhotoDatabaseUtils.insertPhoto(db, width, height, dateTaken, null,
+ mimeType, null));
+
+ // Test normal insert
+ assertTrue(PhotoDatabaseUtils.insertPhoto(db, width, height, dateTaken, null, mimeType,
+ accountId));
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ public void testMetadataConstraints() {
+ SQLiteDatabase db = getWritableDB();
+ db.beginTransaction();
+ try {
+ final String mimeType = "test/test";
+ PhotoDatabaseUtils.insertPhoto(db, 100, 100, 100L, PARENT_ID1, mimeType, 100L);
+ long photoId = PhotoDatabaseUtils.queryPhotoIdFromAlbumId(db, PARENT_ID1);
+
+ // Test NOT NULL PHOTO_ID constraint.
+ assertFalse(PhotoDatabaseUtils.insertMetadata(db, null, "foo", "bar"));
+
+ // Normal insert.
+ assertTrue(PhotoDatabaseUtils.insertMetadata(db, photoId, "foo", "bar"));
+
+ // Test uniqueness constraint.
+ assertFalse(PhotoDatabaseUtils.insertMetadata(db, photoId, "foo", "baz"));
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ public void testAccountsConstraints() {
+ SQLiteDatabase db = getWritableDB();
+ db.beginTransaction();
+ try {
+ assertFalse(PhotoDatabaseUtils.insertAccount(db, null));
+ assertTrue(PhotoDatabaseUtils.insertAccount(db, "hello"));
+ assertTrue(PhotoDatabaseUtils.insertAccount(db, "hello"));
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ public void testUpgrade() {
+ SQLiteDatabase db = getWritableDB();
+ db.beginTransaction();
+ try {
+ assertTrue(PhotoDatabaseUtils.insertAccount(db, "Hello"));
+ assertTrue(PhotoDatabaseUtils.insertAlbum(db, PARENT_ID1, "hello",
+ Albums.VISIBILITY_PRIVATE, 100L));
+ final String mimeType = "test/test";
+ assertTrue(PhotoDatabaseUtils.insertPhoto(db, 100, 100, 100L, PARENT_ID1, mimeType,
+ 100L));
+ // Normal insert.
+ assertTrue(PhotoDatabaseUtils.insertMetadata(db, 100L, "foo", "bar"));
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ mDBHelper.close();
+ Context context = getInstrumentation().getTargetContext();
+ mDBHelper = new PhotoDatabase(context, DB_NAME, PhotoDatabase.DB_VERSION + 1);
+ db = getReadableDB();
+ assertEquals(0, DatabaseUtils.queryNumEntries(db, Accounts.TABLE));
+ assertEquals(0, DatabaseUtils.queryNumEntries(db, Photos.TABLE));
+ assertEquals(0, DatabaseUtils.queryNumEntries(db, Albums.TABLE));
+ assertEquals(0, DatabaseUtils.queryNumEntries(db, Metadata.TABLE));
+ }
+
+ private SQLiteDatabase getReadableDB() {
+ return mDBHelper.getReadableDatabase();
+ }
+
+ private SQLiteDatabase getWritableDB() {
+ return mDBHelper.getWritableDatabase();
+ }
+
+ private void validateTable(String table, String[] projection) {
+ SQLiteDatabase db = getReadableDB();
+ Cursor cursor = db.query(table, projection, null, null, null, null, null);
+ assertNotNull(cursor);
+ assertEquals(cursor.getCount(), 0);
+ assertEquals(cursor.getColumnCount(), projection.length);
+ for (int i = 0; i < projection.length; i++) {
+ assertEquals(cursor.getColumnName(i), projection[i]);
+ }
+ }
+
+
+}
diff --git a/tests/src/com/android/photos/data/PhotoDatabaseUtils.java b/tests/src/com/android/photos/data/PhotoDatabaseUtils.java
new file mode 100644
index 0000000..f7a46d4
--- /dev/null
+++ b/tests/src/com/android/photos/data/PhotoDatabaseUtils.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2013 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.photos.data;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+
+import com.android.photos.data.PhotoProvider.Accounts;
+import com.android.photos.data.PhotoProvider.Albums;
+import com.android.photos.data.PhotoProvider.Metadata;
+import com.android.photos.data.PhotoProvider.Photos;
+
+import junit.framework.AssertionFailedError;
+
+public class PhotoDatabaseUtils {
+ public static String[] PROJECTION_ALBUMS = {
+ Albums._ID,
+ Albums.ACCOUNT_ID,
+ Albums.PARENT_ID,
+ Albums.VISIBILITY,
+ Albums.LOCATION_STRING,
+ Albums.TITLE,
+ Albums.SUMMARY,
+ Albums.DATE_PUBLISHED,
+ Albums.DATE_MODIFIED,
+ };
+
+ public static String[] PROJECTION_METADATA = {
+ Metadata.PHOTO_ID,
+ Metadata.KEY,
+ Metadata.VALUE,
+ };
+
+ public static String[] PROJECTION_PHOTOS = {
+ Photos._ID,
+ Photos.ACCOUNT_ID,
+ Photos.WIDTH,
+ Photos.HEIGHT,
+ Photos.DATE_TAKEN,
+ Photos.ALBUM_ID,
+ Photos.MIME_TYPE,
+ Photos.TITLE,
+ Photos.DATE_MODIFIED,
+ Photos.ROTATION,
+ };
+
+ public static String[] PROJECTION_ACCOUNTS = {
+ Accounts._ID,
+ Accounts.ACCOUNT_NAME,
+ };
+
+ private static String SELECTION_ALBUM_PARENT_ID = Albums.PARENT_ID + " = ?";
+ private static String SELECTION_PHOTO_ALBUM_ID = Photos.ALBUM_ID + " = ?";
+ private static String SELECTION_ACCOUNT_ID = Accounts.ACCOUNT_NAME + " = ?";
+
+ public static long queryAlbumIdFromParentId(SQLiteDatabase db, long parentId) {
+ return queryId(db, Albums.TABLE, PROJECTION_ALBUMS, SELECTION_ALBUM_PARENT_ID, parentId);
+ }
+
+ public static long queryPhotoIdFromAlbumId(SQLiteDatabase db, long albumId) {
+ return queryId(db, Photos.TABLE, PROJECTION_PHOTOS, SELECTION_PHOTO_ALBUM_ID, albumId);
+ }
+
+ public static long queryAccountIdFromName(SQLiteDatabase db, String accountName) {
+ return queryId(db, Accounts.TABLE, PROJECTION_ACCOUNTS, SELECTION_ACCOUNT_ID, accountName);
+ }
+
+ public static long queryId(SQLiteDatabase db, String table, String[] projection,
+ String selection, Object parameter) {
+ String paramString = parameter == null ? null : parameter.toString();
+ String[] selectionArgs = {
+ paramString,
+ };
+ Cursor cursor = db.query(table, projection, selection, selectionArgs, null, null, null);
+ try {
+ if (cursor.getCount() != 1 || !cursor.moveToNext()) {
+ throw new AssertionFailedError("Couldn't find item in table");
+ }
+ long id = cursor.getLong(0);
+ return id;
+ } finally {
+ cursor.close();
+ }
+ }
+
+ public static boolean insertPhoto(SQLiteDatabase db, Integer width, Integer height,
+ Long dateTaken, Long albumId, String mimeType, Long accountId) {
+ ContentValues values = new ContentValues();
+ values.put(Photos.WIDTH, width);
+ values.put(Photos.HEIGHT, height);
+ values.put(Photos.DATE_TAKEN, dateTaken);
+ values.put(Photos.ALBUM_ID, albumId);
+ values.put(Photos.MIME_TYPE, mimeType);
+ values.put(Photos.ACCOUNT_ID, accountId);
+ return db.insert(Photos.TABLE, null, values) != -1;
+ }
+
+ public static boolean insertAlbum(SQLiteDatabase db, Long parentId, String title,
+ Integer privacy, Long accountId) {
+ ContentValues values = new ContentValues();
+ values.put(Albums.PARENT_ID, parentId);
+ values.put(Albums.TITLE, title);
+ values.put(Albums.VISIBILITY, privacy);
+ values.put(Albums.ACCOUNT_ID, accountId);
+ return db.insert(Albums.TABLE, null, values) != -1;
+ }
+
+ public static boolean insertMetadata(SQLiteDatabase db, Long photosId, String key, String value) {
+ ContentValues values = new ContentValues();
+ values.put(Metadata.PHOTO_ID, photosId);
+ values.put(Metadata.KEY, key);
+ values.put(Metadata.VALUE, value);
+ return db.insert(Metadata.TABLE, null, values) != -1;
+ }
+
+ public static boolean insertAccount(SQLiteDatabase db, String name) {
+ ContentValues values = new ContentValues();
+ values.put(Accounts.ACCOUNT_NAME, name);
+ return db.insert(Accounts.TABLE, null, values) != -1;
+ }
+}
diff --git a/tests/src/com/android/photos/data/PhotoProviderTest.java b/tests/src/com/android/photos/data/PhotoProviderTest.java
new file mode 100644
index 0000000..685946e
--- /dev/null
+++ b/tests/src/com/android/photos/data/PhotoProviderTest.java
@@ -0,0 +1,391 @@
+/*
+ * Copyright (C) 2013 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.photos.data;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.BaseColumns;
+import android.test.ProviderTestCase2;
+
+import com.android.photos.data.PhotoProvider.Accounts;
+import com.android.photos.data.PhotoProvider.Albums;
+import com.android.photos.data.PhotoProvider.Metadata;
+import com.android.photos.data.PhotoProvider.Photos;
+
+import java.util.ArrayList;
+
+public class PhotoProviderTest extends ProviderTestCase2<PhotoProvider> {
+ @SuppressWarnings("unused")
+ private static final String TAG = PhotoProviderTest.class.getSimpleName();
+
+ private static final String MIME_TYPE = "test/test";
+ private static final String ALBUM_TITLE = "My Album";
+ private static final long ALBUM_PARENT_ID = 100;
+ private static final String META_KEY = "mykey";
+ private static final String META_VALUE = "myvalue";
+ private static final String ACCOUNT_NAME = "foo@bar.com";
+
+ private static final Uri NO_TABLE_URI = PhotoProvider.BASE_CONTENT_URI;
+ private static final Uri BAD_TABLE_URI = Uri.withAppendedPath(PhotoProvider.BASE_CONTENT_URI,
+ "bad_table");
+
+ private static final String WHERE_METADATA_PHOTOS_ID = Metadata.PHOTO_ID + " = ?";
+ private static final String WHERE_METADATA = Metadata.PHOTO_ID + " = ? AND " + Metadata.KEY
+ + " = ?";
+
+ private long mAlbumId;
+ private long mPhotoId;
+ private long mMetadataId;
+ private long mAccountId;
+
+ private SQLiteOpenHelper mDBHelper;
+ private ContentResolver mResolver;
+ private NotificationWatcher mNotifications = new NotificationWatcher();
+
+ public PhotoProviderTest() {
+ super(PhotoProvider.class, PhotoProvider.AUTHORITY);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mResolver = getMockContentResolver();
+ PhotoProvider provider = (PhotoProvider) getProvider();
+ provider.setMockNotification(mNotifications);
+ mDBHelper = provider.getDatabaseHelper();
+ SQLiteDatabase db = mDBHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ PhotoDatabaseUtils.insertAccount(db, ACCOUNT_NAME);
+ mAccountId = PhotoDatabaseUtils.queryAccountIdFromName(db, ACCOUNT_NAME);
+ PhotoDatabaseUtils.insertAlbum(db, ALBUM_PARENT_ID, ALBUM_TITLE,
+ Albums.VISIBILITY_PRIVATE, mAccountId);
+ mAlbumId = PhotoDatabaseUtils.queryAlbumIdFromParentId(db, ALBUM_PARENT_ID);
+ PhotoDatabaseUtils.insertPhoto(db, 100, 100, System.currentTimeMillis(), mAlbumId,
+ MIME_TYPE, mAccountId);
+ mPhotoId = PhotoDatabaseUtils.queryPhotoIdFromAlbumId(db, mAlbumId);
+ PhotoDatabaseUtils.insertMetadata(db, mPhotoId, META_KEY, META_VALUE);
+ String[] projection = {
+ BaseColumns._ID,
+ };
+ Cursor cursor = db.query(Metadata.TABLE, projection, null, null, null, null, null);
+ cursor.moveToNext();
+ mMetadataId = cursor.getLong(0);
+ cursor.close();
+ db.setTransactionSuccessful();
+ mNotifications.reset();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mDBHelper.close();
+ mDBHelper = null;
+ super.tearDown();
+ getMockContext().deleteDatabase(PhotoProvider.DB_NAME);
+ }
+
+ public void testDelete() {
+ try {
+ mResolver.delete(NO_TABLE_URI, null, null);
+ fail("Exeption should be thrown when no table given");
+ } catch (Exception e) {
+ // expected exception
+ }
+ try {
+ mResolver.delete(BAD_TABLE_URI, null, null);
+ fail("Exeption should be thrown when deleting from a table that doesn't exist");
+ } catch (Exception e) {
+ // expected exception
+ }
+
+ String[] selectionArgs = {
+ String.valueOf(mPhotoId)
+ };
+ // Delete some metadata
+ assertEquals(1,
+ mResolver.delete(Metadata.CONTENT_URI, WHERE_METADATA_PHOTOS_ID, selectionArgs));
+ Uri photoUri = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId);
+ assertEquals(1, mResolver.delete(photoUri, null, null));
+ Uri albumUri = ContentUris.withAppendedId(Albums.CONTENT_URI, mAlbumId);
+ assertEquals(1, mResolver.delete(albumUri, null, null));
+ // now delete something that isn't there
+ assertEquals(0, mResolver.delete(photoUri, null, null));
+ }
+
+ public void testDeleteMetadataId() {
+ Uri metadataUri = ContentUris.withAppendedId(Metadata.CONTENT_URI, mMetadataId);
+ assertEquals(1, mResolver.delete(metadataUri, null, null));
+ Cursor cursor = mResolver.query(Metadata.CONTENT_URI, null, null, null, null);
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+ }
+
+ // Delete the album and ensure that the photos referring to the album are
+ // deleted.
+ public void testDeleteAlbumCascade() {
+ Uri albumUri = ContentUris.withAppendedId(Albums.CONTENT_URI, mAlbumId);
+ mResolver.delete(albumUri, null, null);
+ assertTrue(mNotifications.isNotified(Photos.CONTENT_URI));
+ assertTrue(mNotifications.isNotified(Metadata.CONTENT_URI));
+ assertTrue(mNotifications.isNotified(albumUri));
+ assertEquals(3, mNotifications.notificationCount());
+ Cursor cursor = mResolver.query(Photos.CONTENT_URI, PhotoDatabaseUtils.PROJECTION_PHOTOS,
+ null, null, null);
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+ }
+
+ // Delete all albums and ensure that photos in any album are deleted.
+ public void testDeleteAlbumCascade2() {
+ mResolver.delete(Albums.CONTENT_URI, null, null);
+ assertTrue(mNotifications.isNotified(Photos.CONTENT_URI));
+ assertTrue(mNotifications.isNotified(Metadata.CONTENT_URI));
+ assertTrue(mNotifications.isNotified(Albums.CONTENT_URI));
+ assertEquals(3, mNotifications.notificationCount());
+ Cursor cursor = mResolver.query(Photos.CONTENT_URI, PhotoDatabaseUtils.PROJECTION_PHOTOS,
+ null, null, null);
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+ }
+
+ // Delete a photo and ensure that the metadata for that photo are deleted.
+ public void testDeletePhotoCascade() {
+ Uri photoUri = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId);
+ mResolver.delete(photoUri, null, null);
+ assertTrue(mNotifications.isNotified(photoUri));
+ assertTrue(mNotifications.isNotified(Metadata.CONTENT_URI));
+ assertEquals(2, mNotifications.notificationCount());
+ Cursor cursor = mResolver.query(Metadata.CONTENT_URI,
+ PhotoDatabaseUtils.PROJECTION_METADATA, null, null, null);
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+ }
+
+ public void testDeleteAccountCascade() {
+ Uri accountUri = ContentUris.withAppendedId(Accounts.CONTENT_URI, mAccountId);
+ SQLiteDatabase db = mDBHelper.getWritableDatabase();
+ db.beginTransaction();
+ PhotoDatabaseUtils.insertPhoto(db, 100, 100, System.currentTimeMillis(), null,
+ "image/jpeg", mAccountId);
+ PhotoDatabaseUtils.insertPhoto(db, 100, 100, System.currentTimeMillis(), null,
+ "image/jpeg", 0L);
+ PhotoDatabaseUtils.insertAlbum(db, null, "title", Albums.VISIBILITY_PRIVATE, 10630L);
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ // ensure all pictures are there:
+ Cursor cursor = mResolver.query(Photos.CONTENT_URI, null, null, null, null);
+ assertEquals(3, cursor.getCount());
+ cursor.close();
+ // delete the account
+ assertEquals(1, mResolver.delete(accountUri, null, null));
+ // now ensure that all associated photos were deleted
+ cursor = mResolver.query(Photos.CONTENT_URI, null, null, null, null);
+ assertEquals(1, cursor.getCount());
+ cursor.close();
+ // now ensure all associated albums were deleted.
+ cursor = mResolver.query(Albums.CONTENT_URI, null, null, null, null);
+ assertEquals(1, cursor.getCount());
+ cursor.close();
+ }
+
+ public void testGetType() {
+ // We don't return types for albums
+ assertNull(mResolver.getType(Albums.CONTENT_URI));
+
+ Uri noImage = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId + 1);
+ assertNull(mResolver.getType(noImage));
+
+ Uri image = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId);
+ assertEquals(MIME_TYPE, mResolver.getType(image));
+ }
+
+ public void testInsert() {
+ ContentValues values = new ContentValues();
+ values.put(Albums.TITLE, "add me");
+ values.put(Albums.VISIBILITY, Albums.VISIBILITY_PRIVATE);
+ values.put(Albums.ACCOUNT_ID, 100L);
+ values.put(Albums.DATE_MODIFIED, 100L);
+ values.put(Albums.DATE_PUBLISHED, 100L);
+ values.put(Albums.LOCATION_STRING, "Home");
+ values.put(Albums.TITLE, "hello world");
+ values.putNull(Albums.PARENT_ID);
+ values.put(Albums.SUMMARY, "Nothing much to say about this");
+ Uri insertedUri = mResolver.insert(Albums.CONTENT_URI, values);
+ assertNotNull(insertedUri);
+ Cursor cursor = mResolver.query(insertedUri, PhotoDatabaseUtils.PROJECTION_ALBUMS, null,
+ null, null);
+ assertNotNull(cursor);
+ assertEquals(1, cursor.getCount());
+ cursor.close();
+ }
+
+ public void testUpdate() {
+ ContentValues values = new ContentValues();
+ // Normal update -- use an album.
+ values.put(Albums.TITLE, "foo");
+ Uri albumUri = ContentUris.withAppendedId(Albums.CONTENT_URI, mAlbumId);
+ assertEquals(1, mResolver.update(albumUri, values, null, null));
+ String[] projection = {
+ Albums.TITLE,
+ };
+ Cursor cursor = mResolver.query(albumUri, projection, null, null, null);
+ assertEquals(1, cursor.getCount());
+ assertTrue(cursor.moveToNext());
+ assertEquals("foo", cursor.getString(0));
+ cursor.close();
+
+ // Update a row that doesn't exist.
+ Uri noAlbumUri = ContentUris.withAppendedId(Albums.CONTENT_URI, mAlbumId + 1);
+ values.put(Albums.TITLE, "bar");
+ assertEquals(0, mResolver.update(noAlbumUri, values, null, null));
+
+ // Update a metadata value that exists.
+ ContentValues metadata = new ContentValues();
+ metadata.put(Metadata.PHOTO_ID, mPhotoId);
+ metadata.put(Metadata.KEY, META_KEY);
+ metadata.put(Metadata.VALUE, "new value");
+ assertEquals(1, mResolver.update(Metadata.CONTENT_URI, metadata, null, null));
+
+ projection = new String[] {
+ Metadata.VALUE,
+ };
+
+ String[] selectionArgs = {
+ String.valueOf(mPhotoId), META_KEY,
+ };
+
+ cursor = mResolver.query(Metadata.CONTENT_URI, projection, WHERE_METADATA, selectionArgs,
+ null);
+ assertEquals(1, cursor.getCount());
+ assertTrue(cursor.moveToNext());
+ assertEquals("new value", cursor.getString(0));
+ cursor.close();
+
+ // Update a metadata value that doesn't exist.
+ metadata.put(Metadata.KEY, "other stuff");
+ assertEquals(1, mResolver.update(Metadata.CONTENT_URI, metadata, null, null));
+
+ selectionArgs[1] = "other stuff";
+ cursor = mResolver.query(Metadata.CONTENT_URI, projection, WHERE_METADATA, selectionArgs,
+ null);
+ assertEquals(1, cursor.getCount());
+ assertTrue(cursor.moveToNext());
+ assertEquals("new value", cursor.getString(0));
+ cursor.close();
+
+ // Remove a metadata value using update.
+ metadata.putNull(Metadata.VALUE);
+ assertEquals(1, mResolver.update(Metadata.CONTENT_URI, metadata, null, null));
+ cursor = mResolver.query(Metadata.CONTENT_URI, projection, WHERE_METADATA, selectionArgs,
+ null);
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+ }
+
+ public void testQuery() {
+ // Query a photo that exists.
+ Cursor cursor = mResolver.query(Photos.CONTENT_URI, PhotoDatabaseUtils.PROJECTION_PHOTOS,
+ null, null, null);
+ assertNotNull(cursor);
+ assertEquals(1, cursor.getCount());
+ assertTrue(cursor.moveToNext());
+ assertEquals(mPhotoId, cursor.getLong(0));
+ cursor.close();
+
+ // Query a photo that doesn't exist.
+ Uri noPhotoUri = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId + 1);
+ cursor = mResolver.query(noPhotoUri, PhotoDatabaseUtils.PROJECTION_PHOTOS, null, null,
+ null);
+ assertNotNull(cursor);
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+
+ // Query a photo that exists using selection arguments.
+ String[] selectionArgs = {
+ String.valueOf(mPhotoId),
+ };
+
+ cursor = mResolver.query(Photos.CONTENT_URI, PhotoDatabaseUtils.PROJECTION_PHOTOS,
+ Photos._ID + " = ?", selectionArgs, null);
+ assertNotNull(cursor);
+ assertEquals(1, cursor.getCount());
+ assertTrue(cursor.moveToNext());
+ assertEquals(mPhotoId, cursor.getLong(0));
+ cursor.close();
+ }
+
+ public void testUpdatePhotoNotification() {
+ Uri photoUri = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId);
+ ContentValues values = new ContentValues();
+ values.put(Photos.MIME_TYPE, "not-a/mime-type");
+ mResolver.update(photoUri, values, null, null);
+ assertTrue(mNotifications.isNotified(photoUri));
+ }
+
+ public void testUpdateMetadataNotification() {
+ ContentValues values = new ContentValues();
+ values.put(Metadata.PHOTO_ID, mPhotoId);
+ values.put(Metadata.KEY, META_KEY);
+ values.put(Metadata.VALUE, "hello world");
+ mResolver.update(Metadata.CONTENT_URI, values, null, null);
+ assertTrue(mNotifications.isNotified(Metadata.CONTENT_URI));
+ }
+
+ public void testBatchTransaction() throws RemoteException, OperationApplicationException {
+ ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
+ ContentProviderOperation.Builder insert = ContentProviderOperation
+ .newInsert(Photos.CONTENT_URI);
+ insert.withValue(Photos.WIDTH, 200L);
+ insert.withValue(Photos.HEIGHT, 100L);
+ insert.withValue(Photos.DATE_TAKEN, System.currentTimeMillis());
+ insert.withValue(Photos.ALBUM_ID, 1000L);
+ insert.withValue(Photos.MIME_TYPE, "image/jpg");
+ insert.withValue(Photos.ACCOUNT_ID, 1L);
+ operations.add(insert.build());
+ ContentProviderOperation.Builder update = ContentProviderOperation.newUpdate(Photos.CONTENT_URI);
+ update.withValue(Photos.DATE_MODIFIED, System.currentTimeMillis());
+ String[] whereArgs = {
+ "100",
+ };
+ String where = Photos.WIDTH + " = ?";
+ update.withSelection(where, whereArgs);
+ operations.add(update.build());
+ ContentProviderOperation.Builder delete = ContentProviderOperation
+ .newDelete(Photos.CONTENT_URI);
+ delete.withSelection(where, whereArgs);
+ operations.add(delete.build());
+ mResolver.applyBatch(PhotoProvider.AUTHORITY, operations);
+ assertEquals(3, mNotifications.notificationCount());
+ SQLiteDatabase db = mDBHelper.getReadableDatabase();
+ long id = PhotoDatabaseUtils.queryPhotoIdFromAlbumId(db, 1000L);
+ Uri uri = ContentUris.withAppendedId(Photos.CONTENT_URI, id);
+ assertTrue(mNotifications.isNotified(uri));
+ assertTrue(mNotifications.isNotified(Metadata.CONTENT_URI));
+ assertTrue(mNotifications.isNotified(Photos.CONTENT_URI));
+ }
+
+}
diff --git a/tests/src/com/android/photos/data/TestHelper.java b/tests/src/com/android/photos/data/TestHelper.java
new file mode 100644
index 0000000..338e160
--- /dev/null
+++ b/tests/src/com/android/photos/data/TestHelper.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2013 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.photos.data;
+
+import android.util.Log;
+
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import java.lang.reflect.Method;
+
+public class TestHelper {
+ private static String TAG = TestHelper.class.getSimpleName();
+
+ public interface TestInitialization {
+ void initialize(TestCase testCase);
+ }
+
+ public static void addTests(Class<? extends TestCase> testClass, TestSuite suite,
+ TestInitialization initialization) {
+ for (Method method : testClass.getDeclaredMethods()) {
+ if (method.getName().startsWith("test") && method.getParameterTypes().length == 0) {
+ TestCase test;
+ try {
+ test = testClass.newInstance();
+ test.setName(method.getName());
+ initialization.initialize(test);
+ 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);
+ }
+ }
+ }
+ }
+
+}