diff options
Diffstat (limited to 'tests')
65 files changed, 11131 insertions, 0 deletions
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml new file mode 100644 index 000000000..f44156ea4 --- /dev/null +++ b/tests/AndroidManifest.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.gallery3d.tests"> + + <application + android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.test.InstrumentationTestRunner" + android:targetPackage="com.android.gallery3d" + android:label="Tests for GalleryNew3D application."/> + + <instrumentation android:name="com.android.gallery3d.CameraTestRunner" + android:targetPackage="com.android.gallery3d" + android:label="Camera continuous test runner"/> + + <instrumentation android:name="com.android.gallery3d.exif.ExifTestRunner" + android:targetPackage="com.android.gallery3d" + android:label="Tests for ExifParser."/> + + <instrumentation android:name="com.android.gallery3d.jpegstream.JpegStreamTestRunner" + android:targetPackage="com.android.gallery3d" + android:label="Tests for JpegStream classes."/> + + <instrumentation android:name="com.android.gallery3d.stress.CameraStressTestRunner" + android:targetPackage="com.android.gallery3d" + android:label="Camera stress test runner"/> + + <instrumentation android:name="com.android.photos.data.DataTestRunner" + android:targetPackage="com.android.gallery3d" + android:label="Tests for android photo DataProviders."/> +</manifest> diff --git a/tests/res/raw/android_lawn.mp4 b/tests/res/raw/android_lawn.mp4 Binary files differnew file mode 100644 index 000000000..bdeffbed7 --- /dev/null +++ b/tests/res/raw/android_lawn.mp4 diff --git a/tests/res/raw/galaxy_nexus.jpg b/tests/res/raw/galaxy_nexus.jpg Binary files differnew file mode 100755 index 000000000..de91df696 --- /dev/null +++ b/tests/res/raw/galaxy_nexus.jpg diff --git a/tests/res/raw/jpeg_control.jpg b/tests/res/raw/jpeg_control.jpg Binary files differnew file mode 100644 index 000000000..bb468a77c --- /dev/null +++ b/tests/res/raw/jpeg_control.jpg diff --git a/tests/res/xml/galaxy_nexus.xml b/tests/res/xml/galaxy_nexus.xml new file mode 100644 index 000000000..55dd524a4 --- /dev/null +++ b/tests/res/xml/galaxy_nexus.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<exif> + <tag ifd="IFD0" id="0x0100" name="ImageWidth">2560</tag> + <tag ifd="IFD0" id="0x0101" name="ImageHeight">1920</tag> + <tag ifd="IFD0" id="0x010f" name="Make">google</tag> + <tag ifd="IFD0" id="0x0110" name="Model">Nexus S</tag> + <tag ifd="IFD0" id="0x0112" name="Orientation">1</tag> + <tag ifd="IFD0" id="0x0131" name="Software">MASTER</tag> + <tag ifd="IFD0" id="0x0132" name="ModifyDate">2012:07:30 16:28:42</tag> + <tag ifd="IFD0" id="0x0213" name="YCbCrPositioning">1</tag> + <tag ifd="IFD0" id="0x8769" name="ExifOffset">NO_VALUE</tag> + <tag ifd="ExifIFD" id="0x829a" name="ExposureTime">1/40</tag> + <tag ifd="ExifIFD" id="0x829d" name="FNumber">26/10</tag> + <tag ifd="ExifIFD" id="0x8822" name="ExposureProgram">3</tag> + <tag ifd="ExifIFD" id="0x8827" name="ISO">100</tag> + <tag ifd="ExifIFD" id="0x9000" name="ExifVersion">0220</tag> + <tag ifd="ExifIFD" id="0x9003" name="DateTimeOriginal">2012:07:30 16:28:42</tag> + <tag ifd="ExifIFD" id="0x9004" name="CreateDate">2012:07:30 16:28:42</tag> + <tag ifd="ExifIFD" id="0x9201" name="ShutterSpeedValue">50/10</tag> + <tag ifd="ExifIFD" id="0x9202" name="ApertureValue">30/10</tag> + <tag ifd="ExifIFD" id="0x9203" name="BrightnessValue">30/10</tag> + <tag ifd="ExifIFD" id="0x9204" name="ExposureCompensation">0/0</tag> + <tag ifd="ExifIFD" id="0x9205" name="MaxApertureValue">30/10</tag> + <tag ifd="ExifIFD" id="0x9207" name="MeteringMode">2</tag> + <tag ifd="ExifIFD" id="0x9209" name="Flash">0</tag> + <tag ifd="ExifIFD" id="0x920a" name="FocalLength">343/100</tag> + <tag ifd="ExifIFD" id="0x9286" name="UserComment">IICSAUser comments</tag> + <tag ifd="ExifIFD" id="0xa001" name="ColorSpace">1</tag> + <tag ifd="ExifIFD" id="0xa002" name="ExifImageWidth">2560</tag> + <tag ifd="ExifIFD" id="0xa003" name="ExifImageHeight">1920</tag> + <tag ifd="ExifIFD" id="0xa402" name="ExposureMode">0</tag> + <tag ifd="ExifIFD" id="0xa403" name="WhiteBalance">0</tag> + <tag ifd="ExifIFD" id="0xa406" name="SceneCaptureType">0</tag> + <tag ifd="IFD1" id="0x0100" name="ImageWidth">320</tag> + <tag ifd="IFD1" id="0x0101" name="ImageHeight">240</tag> + <tag ifd="IFD1" id="0x0103" name="Compression">6</tag> + <tag ifd="IFD1" id="0x0112" name="Orientation">1</tag> + <tag ifd="IFD1" id="0x011a" name="XResolution">72/1</tag> + <tag ifd="IFD1" id="0x011b" name="YResolution">72/1</tag> + <tag ifd="IFD1" id="0x0128" name="ResolutionUnit">2</tag> + <tag ifd="IFD1" id="0x0201" name="ThumbnailOffset">690</tag> + <tag ifd="IFD1" id="0x0202" name="ThumbnailLength">10447</tag> +</exif> diff --git a/tests/src/com/android/gallery3d/CameraTestRunner.java b/tests/src/com/android/gallery3d/CameraTestRunner.java new file mode 100755 index 000000000..503233675 --- /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/StressTests.java b/tests/src/com/android/gallery3d/StressTests.java new file mode 100755 index 000000000..b991e9e8d --- /dev/null +++ b/tests/src/com/android/gallery3d/StressTests.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2009 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 com.android.gallery3d.stress.CameraLatency; +import com.android.gallery3d.stress.CameraStartUp; +import com.android.gallery3d.stress.ImageCapture; +import com.android.gallery3d.stress.SwitchPreview; + +import junit.framework.Test; +import junit.framework.TestSuite; + + +/** + * Instrumentation Test Runner for all Camera tests. + * + * Running all tests: + * + * adb shell am instrument \ + * -e class com.android.gallery3d.StressTests \ + * -w com.google.android.gallery3d.tests/com.android.gallery3d.stress.CameraStressTestRunner + */ + +public class StressTests extends TestSuite { + public static Test suite() { + TestSuite result = new TestSuite(); + result.addTestSuite(CameraLatency.class); + result.addTestSuite(CameraStartUp.class); + result.addTestSuite(ImageCapture.class); +// result.addTestSuite(SwitchPreview.class); + return result; + } +} 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 000000000..c7d5daec7 --- /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 000000000..2a911c45b --- /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 000000000..a20ebeb2f --- /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 000000000..bbc569238 --- /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 000000000..5aff2a2b2 --- /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 000000000..8f6a46b8e --- /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 000000000..33dfe96de --- /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 000000000..2901979de --- /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 000000000..fa83c796f --- /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 000000000..27ed4d0de --- /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 000000000..b43d10963 --- /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 000000000..526cfe357 --- /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 000000000..142cc6bf6 --- /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 000000000..01b2a323e --- /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 000000000..96f405ef6 --- /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 000000000..151bdbc99 --- /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 000000000..247ea022b --- /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 000000000..4b5c02941 --- /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 000000000..e6a41ecfa --- /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 000000000..162baea29 --- /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 000000000..da860208b --- /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 000000000..12e9cf77e --- /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 000000000..15de00714 --- /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 000000000..c293c0d4a --- /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 000000000..8d394b5db --- /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 000000000..c8d7bbb1c --- /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 000000000..a57c18840 --- /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 000000000..416c11414 --- /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 000000000..b242217a2 --- /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 000000000..4b66040dd --- /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 000000000..9e7955418 --- /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 000000000..ae60a91a7 --- /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 000000000..ed3b08aaa --- /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 000000000..2afaf39eb --- /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 000000000..befba4ccd --- /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/stress/CameraLatency.java b/tests/src/com/android/gallery3d/stress/CameraLatency.java new file mode 100755 index 000000000..2cdc2f1b7 --- /dev/null +++ b/tests/src/com/android/gallery3d/stress/CameraLatency.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2009 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.stress; + +import com.android.camera.CameraActivity; + +import android.app.Instrumentation; +import android.os.Environment; +import android.test.ActivityInstrumentationTestCase2; +import android.test.suitebuilder.annotation.LargeTest; +import android.util.Log; +import android.view.KeyEvent; + +import java.io.BufferedWriter; +import java.io.FileWriter; + +/** + * Junit / Instrumentation test case for camera test + * + */ + +public class CameraLatency extends ActivityInstrumentationTestCase2 <CameraActivity> { + private String TAG = "CameraLatency"; + private static final int TOTAL_NUMBER_OF_IMAGECAPTURE = 20; + private static final long WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN = 4000; + private static final String CAMERA_TEST_OUTPUT_FILE = + Environment.getExternalStorageDirectory().toString() + "/mediaStressOut.txt"; + + private long mTotalAutoFocusTime; + private long mTotalShutterLag; + private long mTotalShutterToPictureDisplayedTime; + private long mTotalPictureDisplayedToJpegCallbackTime; + private long mTotalJpegCallbackFinishTime; + private long mAvgAutoFocusTime; + private long mAvgShutterLag = mTotalShutterLag; + private long mAvgShutterToPictureDisplayedTime; + private long mAvgPictureDisplayedToJpegCallbackTime; + private long mAvgJpegCallbackFinishTime; + + public CameraLatency() { + super(CameraActivity.class); + } + + @Override + protected void setUp() throws Exception { + getActivity(); + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + public void testImageCapture() { + Log.v(TAG, "start testImageCapture test"); + Instrumentation inst = getInstrumentation(); + inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN); + try { + for (int i = 0; i < TOTAL_NUMBER_OF_IMAGECAPTURE; i++) { + Thread.sleep(WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN); + inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER); + Thread.sleep(WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN); + //skip the first measurement + if (i != 0) { + CameraActivity c = getActivity(); + + // if any of the latency var accessor methods return -1 then the + // camera is set to a different module other than PhotoModule so + // skip the shot and try again + if (c.getAutoFocusTime() != -1) { + mTotalAutoFocusTime += c.getAutoFocusTime(); + mTotalShutterLag += c.getShutterLag(); + mTotalShutterToPictureDisplayedTime += + c.getShutterToPictureDisplayedTime(); + mTotalPictureDisplayedToJpegCallbackTime += + c.getPictureDisplayedToJpegCallbackTime(); + mTotalJpegCallbackFinishTime += c.getJpegCallbackFinishTime(); + } + else { + i--; + continue; + } + } + } + } catch (Exception e) { + Log.v(TAG, "Got exception", e); + } + //ToDO: yslau + //1) Need to get the baseline from the cupcake so that we can add the + //failure condition of the camera latency. + //2) Only count those number with succesful capture. Set the timer to invalid + //before capture and ignore them if the value is invalid + int numberofRun = TOTAL_NUMBER_OF_IMAGECAPTURE - 1; + mAvgAutoFocusTime = mTotalAutoFocusTime / numberofRun; + mAvgShutterLag = mTotalShutterLag / numberofRun; + mAvgShutterToPictureDisplayedTime = + mTotalShutterToPictureDisplayedTime / numberofRun; + mAvgPictureDisplayedToJpegCallbackTime = + mTotalPictureDisplayedToJpegCallbackTime / numberofRun; + mAvgJpegCallbackFinishTime = + mTotalJpegCallbackFinishTime / numberofRun; + + try { + FileWriter fstream = null; + fstream = new FileWriter(CAMERA_TEST_OUTPUT_FILE, true); + BufferedWriter out = new BufferedWriter(fstream); + out.write("Camera Latency : \n"); + out.write("Number of loop: " + TOTAL_NUMBER_OF_IMAGECAPTURE + "\n"); + out.write("Avg AutoFocus = " + mAvgAutoFocusTime + "\n"); + out.write("Avg mShutterLag = " + mAvgShutterLag + "\n"); + out.write("Avg mShutterToPictureDisplayedTime = " + + mAvgShutterToPictureDisplayedTime + "\n"); + out.write("Avg mPictureDisplayedToJpegCallbackTime = " + + mAvgPictureDisplayedToJpegCallbackTime + "\n"); + out.write("Avg mJpegCallbackFinishTime = " + + mAvgJpegCallbackFinishTime + "\n"); + out.close(); + fstream.close(); + } catch (Exception e) { + fail("Camera Latency write output to file"); + } + Log.v(TAG, "The Image capture wait time = " + + WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN); + Log.v(TAG, "Avg AutoFocus = " + mAvgAutoFocusTime); + Log.v(TAG, "Avg mShutterLag = " + mAvgShutterLag); + Log.v(TAG, "Avg mShutterToPictureDisplayedTime = " + + mAvgShutterToPictureDisplayedTime); + Log.v(TAG, "Avg mPictureDisplayedToJpegCallbackTime = " + + mAvgPictureDisplayedToJpegCallbackTime); + Log.v(TAG, "Avg mJpegCallbackFinishTime = " + mAvgJpegCallbackFinishTime); + } +} + diff --git a/tests/src/com/android/gallery3d/stress/CameraStartUp.java b/tests/src/com/android/gallery3d/stress/CameraStartUp.java new file mode 100644 index 000000000..3ca163227 --- /dev/null +++ b/tests/src/com/android/gallery3d/stress/CameraStartUp.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2009 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.stress; + +import com.android.camera.CameraActivity; + +import android.app.Activity; +import android.app.Instrumentation; +import android.content.Intent; +import android.os.Environment; +import android.provider.MediaStore; +import android.test.InstrumentationTestCase; +import android.test.suitebuilder.annotation.LargeTest; +import android.util.Log; + +import java.io.FileWriter; +import java.io.BufferedWriter; + +/** + * Test cases to measure the camera and video recorder startup time. + */ +public class CameraStartUp extends InstrumentationTestCase { + + private static final int TOTAL_NUMBER_OF_STARTUP = 20; + + private String TAG = "CameraStartUp"; + private static final String CAMERA_TEST_OUTPUT_FILE = + Environment.getExternalStorageDirectory().toString() + "/mediaStressOut.txt"; + private static int WAIT_TIME_FOR_PREVIEW = 1500; //1.5 second + + private long launchCamera() { + long startupTime = 0; + try { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setClass(getInstrumentation().getTargetContext(), CameraActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + long beforeStart = System.currentTimeMillis(); + Instrumentation inst = getInstrumentation(); + Activity cameraActivity = inst.startActivitySync(intent); + long cameraStarted = System.currentTimeMillis(); + Thread.sleep(WAIT_TIME_FOR_PREVIEW); + cameraActivity.finish(); + startupTime = cameraStarted - beforeStart; + Thread.sleep(1000); + Log.v(TAG, "camera startup time: " + startupTime); + } catch (Exception e) { + Log.v(TAG, "Got exception", e); + fail("Fails to get the output file"); + } + return startupTime; + } + + private long launchVideo() { + long startupTime = 0; + + try { + Intent intent = new Intent(MediaStore.INTENT_ACTION_VIDEO_CAMERA); + intent.setClass(getInstrumentation().getTargetContext(), CameraActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + long beforeStart = System.currentTimeMillis(); + Instrumentation inst = getInstrumentation(); + Activity recorderActivity = inst.startActivitySync(intent); + long cameraStarted = System.currentTimeMillis(); + recorderActivity.finish(); + startupTime = cameraStarted - beforeStart; + Log.v(TAG, "Video Startup Time = " + startupTime); + // wait for 1s to make sure it reach a clean stage + Thread.sleep(WAIT_TIME_FOR_PREVIEW); + Log.v(TAG, "video startup time: " + startupTime); + } catch (Exception e) { + Log.v(TAG, "Got exception", e); + fail("Fails to launch video output file"); + } + return startupTime; + } + + private void writeToOutputFile(long totalStartupTime, + String individualStartupTime, boolean firstStartUp, String Type) throws Exception { + // TODO (yslau) : Need to integrate the output data with central + // dashboard + try { + FileWriter fstream = null; + fstream = new FileWriter(CAMERA_TEST_OUTPUT_FILE, true); + BufferedWriter out = new BufferedWriter(fstream); + if (firstStartUp) { + out.write("First " + Type + " Startup: " + totalStartupTime + "\n"); + } else { + long averageStartupTime = totalStartupTime / (TOTAL_NUMBER_OF_STARTUP -1); + out.write(Type + "startup time: " + "\n"); + out.write("Number of loop: " + (TOTAL_NUMBER_OF_STARTUP -1) + "\n"); + out.write(individualStartupTime + "\n\n"); + out.write(Type + " average startup time: " + averageStartupTime + " ms\n\n"); + } + out.close(); + fstream.close(); + } catch (Exception e) { + fail("Camera write output to file"); + } + } + + public void testLaunchVideo() throws Exception { + String individualStartupTime; + individualStartupTime = "Individual Video Startup Time = "; + long totalStartupTime = 0; + long startupTime = 0; + for (int i = 0; i < TOTAL_NUMBER_OF_STARTUP; i++) { + if (i == 0) { + // Capture the first startup time individually + long firstStartUpTime = launchVideo(); + writeToOutputFile(firstStartUpTime, "na", true, "Video"); + } else { + startupTime = launchVideo(); + totalStartupTime += startupTime; + individualStartupTime += startupTime + " ,"; + } + } + Log.v(TAG, "totalStartupTime =" + totalStartupTime); + writeToOutputFile(totalStartupTime, individualStartupTime, false, "Video"); + } + + public void testLaunchCamera() throws Exception { + String individualStartupTime; + individualStartupTime = "Individual Camera Startup Time = "; + long totalStartupTime = 0; + long startupTime = 0; + for (int i = 0; i < TOTAL_NUMBER_OF_STARTUP; i++) { + if (i == 0) { + // Capture the first startup time individually + long firstStartUpTime = launchCamera(); + writeToOutputFile(firstStartUpTime, "na", true, "Camera"); + } else { + startupTime = launchCamera(); + totalStartupTime += startupTime; + individualStartupTime += startupTime + " ,"; + } + } + Log.v(TAG, "totalStartupTime =" + totalStartupTime); + writeToOutputFile(totalStartupTime, + individualStartupTime, false, "Camera"); + } +} diff --git a/tests/src/com/android/gallery3d/stress/CameraStressTestRunner.java b/tests/src/com/android/gallery3d/stress/CameraStressTestRunner.java new file mode 100755 index 000000000..d3fb10dad --- /dev/null +++ b/tests/src/com/android/gallery3d/stress/CameraStressTestRunner.java @@ -0,0 +1,61 @@ +/* + * 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.stress; + +import android.os.Bundle; +import android.test.InstrumentationTestRunner; +import android.test.InstrumentationTestSuite; +import junit.framework.TestSuite; + +public class CameraStressTestRunner extends InstrumentationTestRunner { + + // Default recorder settings + public static int mVideoDuration = 20000; // set default to 20 seconds + public static int mVideoIterations = 1; // set default to 1 video + public static int mImageIterations = 10; // set default to 10 images + + @Override + public TestSuite getAllTests() { + TestSuite suite = new InstrumentationTestSuite(this); + suite.addTestSuite(ImageCapture.class); + suite.addTestSuite(VideoCapture.class); + return suite; + } + + @Override + public ClassLoader getLoader() { + return CameraStressTestRunner.class.getClassLoader(); + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + String video_iterations = (String) icicle.get("video_iterations"); + String image_iterations = (String) icicle.get("image_iterations"); + String video_duration = (String) icicle.get("video_duration"); + + if ( video_iterations != null ) { + mVideoIterations = Integer.parseInt(video_iterations); + } + if ( image_iterations != null) { + mImageIterations = Integer.parseInt(image_iterations); + } + if ( video_duration != null) { + mVideoDuration = Integer.parseInt(video_duration); + } + } +} diff --git a/tests/src/com/android/gallery3d/stress/ImageCapture.java b/tests/src/com/android/gallery3d/stress/ImageCapture.java new file mode 100755 index 000000000..5a9ee6a6c --- /dev/null +++ b/tests/src/com/android/gallery3d/stress/ImageCapture.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2009 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.stress; + +import com.android.camera.CameraActivity; +import com.android.gallery3d.stress.CameraStressTestRunner; + +import android.app.Instrumentation; +import android.content.Intent; +import android.test.ActivityInstrumentationTestCase2; +import android.test.suitebuilder.annotation.LargeTest; +import android.util.Log; +import android.view.KeyEvent; +import android.app.Activity; + +/** + * Junit / Instrumentation test case for camera test + * + * Running the test suite: + * + * adb shell am instrument \ + * -e class com.android.camera.stress.ImageCapture \ + * -w com.google.android.camera.tests/android.test.InstrumentationTestRunner + * + */ + +public class ImageCapture extends ActivityInstrumentationTestCase2 <CameraActivity> { + private String TAG = "ImageCapture"; + private static final long WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN = 1500; //1.5 sedconds + private static final long WAIT_FOR_SWITCH_CAMERA = 3000; //3 seconds + + private TestUtil testUtil = new TestUtil(); + + // Private intent extras. + private final static String EXTRAS_CAMERA_FACING = + "android.intent.extras.CAMERA_FACING"; + + public ImageCapture() { + super(CameraActivity.class); + } + + @Override + protected void setUp() throws Exception { + testUtil.prepareOutputFile(); + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + testUtil.closeOutputFile(); + super.tearDown(); + } + + public void captureImages(String reportTag, Instrumentation inst) { + int total_num_of_images = CameraStressTestRunner.mImageIterations; + Log.v(TAG, "no of images = " + total_num_of_images); + + //TODO(yslau): Need to integrate the outoput with the central dashboard, + //write to a txt file as a temp solution + boolean memoryResult = false; + KeyEvent focusEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_FOCUS); + + try { + testUtil.writeReportHeader(reportTag, total_num_of_images); + for (int i = 0; i < total_num_of_images; i++) { + Thread.sleep(WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN); + inst.sendKeySync(focusEvent); + inst.sendCharacterSync(KeyEvent.KEYCODE_CAMERA); + Thread.sleep(WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN); + testUtil.writeResult(i); + } + } catch (Exception e) { + Log.v(TAG, "Got exception: " + e.toString()); + assertTrue("testImageCapture", false); + } + } + + public void testBackImageCapture() throws Exception { + Instrumentation inst = getInstrumentation(); + Intent intent = new Intent(); + + intent.setClass(getInstrumentation().getTargetContext(), CameraActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(EXTRAS_CAMERA_FACING, + android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK); + Activity act = inst.startActivitySync(intent); + Thread.sleep(WAIT_FOR_SWITCH_CAMERA); + captureImages("Back Camera Image Capture\n", inst); + act.finish(); + } + + public void testFrontImageCapture() throws Exception { + Instrumentation inst = getInstrumentation(); + Intent intent = new Intent(); + + intent.setClass(getInstrumentation().getTargetContext(), CameraActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(EXTRAS_CAMERA_FACING, + android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT); + Activity act = inst.startActivitySync(intent); + Thread.sleep(WAIT_FOR_SWITCH_CAMERA); + captureImages("Front Camera Image Capture\n", inst); + act.finish(); + } +} diff --git a/tests/src/com/android/gallery3d/stress/ShotToShotLatency.java b/tests/src/com/android/gallery3d/stress/ShotToShotLatency.java new file mode 100644 index 000000000..0d5749e7d --- /dev/null +++ b/tests/src/com/android/gallery3d/stress/ShotToShotLatency.java @@ -0,0 +1,142 @@ +/* + * 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.stress; + +import android.app.Instrumentation; +import android.os.Environment; +import android.test.ActivityInstrumentationTestCase2; +import android.test.suitebuilder.annotation.LargeTest; +import android.util.Log; +import android.view.KeyEvent; +import com.android.camera.CameraActivity; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FilenameFilter; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; + +/** + * Junit / Instrumentation test case for measuring camera shot to shot latency + */ +public class ShotToShotLatency extends ActivityInstrumentationTestCase2<CameraActivity> { + private String TAG = "ShotToShotLatency"; + private static final int TOTAL_NUMBER_OF_SNAPSHOTS = 250; + private static final long SNAPSHOT_WAIT = 1000; + private static final String CAMERA_TEST_OUTPUT_FILE = + Environment.getExternalStorageDirectory().toString() + "/mediaStressOut.txt"; + private static final String CAMERA_IMAGE_DIRECTORY = + Environment.getExternalStorageDirectory().toString() + "/DCIM/Camera/"; + + public ShotToShotLatency() { + super(CameraActivity.class); + } + + @Override + protected void setUp() throws Exception { + getActivity(); + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + private void cleanupLatencyImages() { + try { + File sdcard = new File(CAMERA_IMAGE_DIRECTORY); + File[] pics = null; + FilenameFilter filter = new FilenameFilter() { + public boolean accept(File dir, String name) { + return name.endsWith(".jpg"); + } + }; + pics = sdcard.listFiles(filter); + for (File f : pics) { + f.delete(); + } + } catch (SecurityException e) { + Log.e(TAG, "Security manager access violation: " + e.toString()); + } + } + + private void sleep(long time) { + try { + Thread.sleep(time); + } catch (InterruptedException e) { + Log.e(TAG, "Sleep InterruptedException " + e.toString()); + } + } + + public void testShotToShotLatency() { + long sigmaOfDiffFromMeanSquared = 0; + double mean = 0; + double standardDeviation = 0; + ArrayList<Long> captureTimes = new ArrayList<Long>(); + ArrayList<Long> latencyTimes = new ArrayList<Long>(); + + Log.v(TAG, "start testShotToShotLatency test"); + Instrumentation inst = getInstrumentation(); + + // Generate data points + for (int i = 0; i < TOTAL_NUMBER_OF_SNAPSHOTS; i++) { + inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER); + sleep(SNAPSHOT_WAIT); + CameraActivity c = getActivity(); + if (c.getCaptureStartTime() > 0) { + captureTimes.add(c.getCaptureStartTime()); + } + } + + // Calculate latencies + for (int j = 1; j < captureTimes.size(); j++) { + latencyTimes.add(captureTimes.get(j) - captureTimes.get(j - 1)); + } + + // Crunch numbers + for (long dataPoint : latencyTimes) { + mean += (double) dataPoint; + } + mean /= latencyTimes.size(); + + for (long dataPoint : latencyTimes) { + sigmaOfDiffFromMeanSquared += (dataPoint - mean) * (dataPoint - mean); + } + standardDeviation = Math.sqrt(sigmaOfDiffFromMeanSquared / latencyTimes.size()); + + // Report statistics + File outFile = new File(CAMERA_TEST_OUTPUT_FILE); + BufferedWriter output = null; + try { + output = new BufferedWriter(new FileWriter(outFile, true)); + output.write("Shot to shot latency - mean: " + mean + "\n"); + output.write("Shot to shot latency - standard deviation: " + standardDeviation + "\n"); + cleanupLatencyImages(); + } catch (IOException e) { + Log.e(TAG, "testShotToShotLatency IOException writing to log " + e.toString()); + } finally { + try { + if (output != null) { + output.close(); + } + } catch (IOException e) { + Log.e(TAG, "Error closing file: " + e.toString()); + } + } + } +} diff --git a/tests/src/com/android/gallery3d/stress/SwitchPreview.java b/tests/src/com/android/gallery3d/stress/SwitchPreview.java new file mode 100755 index 000000000..3545f3b3e --- /dev/null +++ b/tests/src/com/android/gallery3d/stress/SwitchPreview.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2009 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.stress; + +import com.android.camera.CameraActivity; + +import android.app.Instrumentation; +import android.content.Intent; +import android.provider.MediaStore; +import android.test.ActivityInstrumentationTestCase2; +import android.test.suitebuilder.annotation.LargeTest; +import android.os.Environment; +import android.util.Log; + +import java.io.BufferedWriter; +import java.io.FileWriter; + +/** + * Junit / Instrumentation test case for camera test + * + * Running the test suite: + * + * adb shell am instrument \ + * -e class com.android.camera.stress.SwitchPreview \ + * -w com.android.camera.tests/com.android.camera.stress.CameraStressTestRunner + * + */ +public class SwitchPreview extends ActivityInstrumentationTestCase2 <CameraActivity>{ + private String TAG = "SwitchPreview"; + private static final int TOTAL_NUMBER_OF_SWITCHING = 200; + private static final long WAIT_FOR_PREVIEW = 4000; + + private static final String CAMERA_TEST_OUTPUT_FILE = + Environment.getExternalStorageDirectory().toString() + "/mediaStressOut.txt"; + private BufferedWriter mOut; + private FileWriter mfstream; + + public SwitchPreview() { + super(CameraActivity.class); + } + + @Override + protected void setUp() throws Exception { + getActivity(); + prepareOutputFile(); + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + getActivity().finish(); + closeOutputFile(); + super.tearDown(); + } + + private void prepareOutputFile(){ + try{ + mfstream = new FileWriter(CAMERA_TEST_OUTPUT_FILE, true); + mOut = new BufferedWriter(mfstream); + } catch (Exception e){ + assertTrue("Camera Switch Mode", false); + } + } + + private void closeOutputFile() { + try { + mOut.write("\n"); + mOut.close(); + mfstream.close(); + } catch (Exception e) { + assertTrue("CameraSwitchMode close output", false); + } + } + + public void testSwitchMode() { + //Switching the video and the video recorder mode + Instrumentation inst = getInstrumentation(); + try{ + mOut.write("Camera Switch Mode:\n"); + mOut.write("No of loops :" + TOTAL_NUMBER_OF_SWITCHING + "\n"); + mOut.write("loop: "); + for (int i=0; i< TOTAL_NUMBER_OF_SWITCHING; i++) { + Thread.sleep(WAIT_FOR_PREVIEW); + Intent intent = new Intent(MediaStore.INTENT_ACTION_VIDEO_CAMERA); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.setClass(getInstrumentation().getTargetContext(), + CameraActivity.class); + getActivity().startActivity(intent); + Thread.sleep(WAIT_FOR_PREVIEW); + intent = new Intent(); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.setClass(getInstrumentation().getTargetContext(), + CameraActivity.class); + getActivity().startActivity(intent); + mOut.write(" ," + i); + mOut.flush(); + } + } catch (Exception e){ + Log.v(TAG, "Got exception", e); + } + } +} diff --git a/tests/src/com/android/gallery3d/stress/TestUtil.java b/tests/src/com/android/gallery3d/stress/TestUtil.java new file mode 100644 index 000000000..56ab715f7 --- /dev/null +++ b/tests/src/com/android/gallery3d/stress/TestUtil.java @@ -0,0 +1,57 @@ +/* + * 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.stress; + +import android.os.Environment; +import java.io.FileWriter; +import java.io.BufferedWriter; + + +/** + * Collection of utility functions used for the test. + */ +public class TestUtil { + public BufferedWriter mOut; + public FileWriter mfstream; + + public TestUtil() { + } + + public void prepareOutputFile() throws Exception { + String camera_test_output_file = + Environment.getExternalStorageDirectory().toString() + "/mediaStressOut.txt"; + mfstream = new FileWriter(camera_test_output_file, true); + mOut = new BufferedWriter(mfstream); + } + + public void closeOutputFile() throws Exception { + mOut.write("\n"); + mOut.close(); + mfstream.close(); + } + + public void writeReportHeader(String reportTag, int iteration) throws Exception { + mOut.write(reportTag); + mOut.write("No of loops :" + iteration + "\n"); + mOut.write("loop: "); + } + + public void writeResult(int iteration) throws Exception { + mOut.write(" ," + iteration); + mOut.flush(); + } +} diff --git a/tests/src/com/android/gallery3d/stress/VideoCapture.java b/tests/src/com/android/gallery3d/stress/VideoCapture.java new file mode 100755 index 000000000..8211badf7 --- /dev/null +++ b/tests/src/com/android/gallery3d/stress/VideoCapture.java @@ -0,0 +1,112 @@ +/* + * 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.stress; + +import com.android.camera.CameraActivity; +import com.android.gallery3d.stress.TestUtil; + +import android.app.Activity; +import android.app.Instrumentation; +import android.content.Intent; +import android.provider.MediaStore; +import android.test.ActivityInstrumentationTestCase2; +import android.test.suitebuilder.annotation.LargeTest; +import android.view.KeyEvent; + +import com.android.gallery3d.stress.CameraStressTestRunner; + +/** + * Junit / Instrumentation test case for camera test + * + * Running the test suite: + * + * adb shell am instrument \ + * -e class com.android.camera.stress.VideoCapture \ + * -w com.google.android.camera.tests/android.test.InstrumentationTestRunner + * + */ + +public class VideoCapture extends ActivityInstrumentationTestCase2 <CameraActivity> { + private static final long WAIT_FOR_PREVIEW = 1500; //1.5 seconds + private static final long WAIT_FOR_SWITCH_CAMERA = 3000; //2 seconds + + // Private intent extras which control the camera facing. + private final static String EXTRAS_CAMERA_FACING = + "android.intent.extras.CAMERA_FACING"; + + private TestUtil testUtil = new TestUtil(); + + public VideoCapture() { + super(CameraActivity.class); + } + + @Override + protected void setUp() throws Exception { + testUtil.prepareOutputFile(); + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + testUtil.closeOutputFile(); + super.tearDown(); + } + + public void captureVideos(String reportTag, Instrumentation inst) throws Exception{ + boolean memoryResult = false; + int total_num_of_videos = CameraStressTestRunner.mVideoIterations; + int video_duration = CameraStressTestRunner.mVideoDuration; + testUtil.writeReportHeader(reportTag, total_num_of_videos); + + for (int i = 0; i < total_num_of_videos; i++) { + Thread.sleep(WAIT_FOR_PREVIEW); + // record a video + inst.sendCharacterSync(KeyEvent.KEYCODE_CAMERA); + Thread.sleep(video_duration); + inst.sendCharacterSync(KeyEvent.KEYCODE_CAMERA); + testUtil.writeResult(i); + } + } + + public void testBackVideoCapture() throws Exception { + Instrumentation inst = getInstrumentation(); + Intent intent = new Intent(MediaStore.INTENT_ACTION_VIDEO_CAMERA); + + intent.setClass(getInstrumentation().getTargetContext(), CameraActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(EXTRAS_CAMERA_FACING, + android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK); + Activity act = inst.startActivitySync(intent); + Thread.sleep(WAIT_FOR_SWITCH_CAMERA); + captureVideos("Back Camera Video Capture\n", inst); + act.finish(); + } + + public void testFrontVideoCapture() throws Exception { + Instrumentation inst = getInstrumentation(); + Intent intent = new Intent(MediaStore.INTENT_ACTION_VIDEO_CAMERA); + + intent.setClass(getInstrumentation().getTargetContext(), CameraActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(EXTRAS_CAMERA_FACING, + android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT); + Activity act = inst.startActivitySync(intent); + Thread.sleep(WAIT_FOR_SWITCH_CAMERA); + captureVideos("Front Camera Video Capture\n", inst); + act.finish(); + } +} 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 000000000..01f0350d2 --- /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 000000000..da78e14ec --- /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 000000000..25e7bca5b --- /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 000000000..9b7488f05 --- /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 000000000..b17b25440 --- /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 000000000..6c78556e1 --- /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 000000000..b8fb05fc2 --- /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 000000000..83e605006 --- /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 000000000..798b905f5 --- /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 000000000..10618d67d --- /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 000000000..9e7112807 --- /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 000000000..e7c168947 --- /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 000000000..f7a46d419 --- /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 000000000..685946ef0 --- /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 000000000..338e160cf --- /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); + } + } + } + } + +} |