Add tests for Animation, LocalMediaSet, Utils.

Change-Id: Iad465ab7475a39b8fdb0e7e8da770fc7c91a35c7
diff --git a/new3d/src/com/android/gallery3d/anim/Animation.java b/new3d/src/com/android/gallery3d/anim/Animation.java
index 637d461..9cbef24 100644
--- a/new3d/src/com/android/gallery3d/anim/Animation.java
+++ b/new3d/src/com/android/gallery3d/anim/Animation.java
@@ -4,9 +4,34 @@
 
 import com.android.gallery3d.ui.Util;
 
+// Animation calculates a value according to the current input time.
+//
+// 1. First we need to use setDuration(int) to set the duration of the
+//    animation. The duration is in milliseconds.
+// 2. Then we should call start(). The actual start time is the first value
+//    passed to calculate(long).
+// 3. Each time we want to get an animation value, we call
+//    calculate(long currentTimeMillis) to ask the Animation to calculate it.
+//    The parameter passed to calculate(long) should be nonnegative.
+// 4. Use get() to get that value.
+//
+// In step 3, onCalculate(float progress) is called so subclasses can calculate
+// the value according to progress (progress is a value in [0,1]).
+//
+// Before onCalculate(float) is called, There is an optional interpolator which
+// can change the progress value. The interpolator can be set by
+// setInterpolator(Interpolator). If the interpolator is used, the value passed
+// to onCalculate may be (for example, the overshoot effect).
+//
+// The isActive() method returns true after the animation start() is called and
+// before calculate is passed a value which reaches the duration of the
+// animation.
+//
+// The start() method can be called again to restart the Animation.
+//
 abstract public class Animation {
-    private static final long ANIMATION_START = 0;
-    private static final long NO_ANIMATION = -1;
+    private static final long ANIMATION_START = -1;
+    private static final long NO_ANIMATION = -2;
 
     private long mStartTime = NO_ANIMATION;
     private int mDuration;
diff --git a/new3d/src/com/android/gallery3d/data/AbstractMediaItem.java b/new3d/src/com/android/gallery3d/data/AbstractMediaItem.java
index f68f9c3..b584cd9 100644
--- a/new3d/src/com/android/gallery3d/data/AbstractMediaItem.java
+++ b/new3d/src/com/android/gallery3d/data/AbstractMediaItem.java
@@ -1,5 +1,9 @@
 package com.android.gallery3d.data;
 
+//
+// AbstractMediaItem is an abstract class captures those common fields
+// in ImageMediaItem and VideoMediaItem.
+//
 public abstract class AbstractMediaItem implements MediaItem {
     static final int FULLIMAGE_TARGET_SIZE = 1024;
     static final int FULLIMAGE_MAX_NUM_PIXELS = 3 * 1024 * 1024;
diff --git a/new3d/src/com/android/gallery3d/data/LocalMediaSet.java b/new3d/src/com/android/gallery3d/data/LocalMediaSet.java
index 497c174..0a265c4 100644
--- a/new3d/src/com/android/gallery3d/data/LocalMediaSet.java
+++ b/new3d/src/com/android/gallery3d/data/LocalMediaSet.java
@@ -7,6 +7,13 @@
 import java.util.Iterator;
 import java.util.Map;
 
+//
+// LocalMediaSet is a MediaSet which is obtained from MediaProvider.
+// Each LocalMediaSet is identified by a bucket id.
+// It is populated by addMediaItem(MediaItem) and addSubMediaSet(LocalMediaSet).
+//
+// getSubMediaSetById(int setId) returns a sub-MediaSet given a bucket id.
+//
 public class LocalMediaSet implements MediaSet {
     public static final int ROOT_SET_ID = -1;
     private static final String TAG = "LocalMediaSet";
@@ -33,12 +40,33 @@
     }
 
     public MediaItem[] getCoverMediaItems() {
-        int size = Math.min(getMediaItemCount(), MAX_NUM_COVERED_ITEMS);
-        MediaItem[] coveredItems = new MediaItem[size];
-        for (int i = 0; i < size; ++i) {
-            coveredItems[i] = getMediaItem(i);
+        MediaItem[] coverItems = new MediaItem[MAX_NUM_COVERED_ITEMS];
+        int filled = fillCoverMediaItems(coverItems, 0);
+        if (filled < MAX_NUM_COVERED_ITEMS) {
+            MediaItem[] result = new MediaItem[filled];
+            System.arraycopy(coverItems, 0, result, 0, filled);
+            return result;
+        } else {
+            return coverItems;
         }
-        return coveredItems;
+    }
+
+    private int fillCoverMediaItems(MediaItem[] items, int offset) {
+        // Fill from my MediaItems.
+        int size = Math.min(getMediaItemCount(), items.length - offset);
+        for (int i = 0; i < size; ++i) {
+            items[offset + i] = getMediaItem(i);
+        }
+        if (offset + size == items.length) return size;
+
+        // Fill from sub-MediaSets.
+        int n = getSubMediaSetCount();
+        for (int i = 0; i < n; ++i) {
+            size += mSubMediaSets.get(i).fillCoverMediaItems(items, offset + size);
+            if (offset + size == items.length) break;
+        }
+
+        return size;
     }
 
     public MediaItem getMediaItem(int index) {
@@ -74,7 +102,7 @@
     public int getTotalMediaItemCount() {
         int totalItemCount = mMediaItems.size();
         for (LocalMediaSet set : mSubMediaSets) {
-            totalItemCount += set.getMediaItemCount();
+            totalItemCount += set.getTotalMediaItemCount();
         }
         return totalItemCount;
     }
diff --git a/new3d/src/com/android/gallery3d/data/MediaSet.java b/new3d/src/com/android/gallery3d/data/MediaSet.java
index be53110..3dc8d58 100644
--- a/new3d/src/com/android/gallery3d/data/MediaSet.java
+++ b/new3d/src/com/android/gallery3d/data/MediaSet.java
@@ -1,22 +1,32 @@
 package com.android.gallery3d.data;
 
+//
+// MediaSet is a directory-like data structure.
+// It contains MediaItems and sub-MediaSets.
+//
+// getTotalMediaItemCount() returns the number of all MediaItems, including
+// those in sub-MediaSets.
+//
+// getCoverMediaItems() return a few representative MediaItems for this
+// MediaSet.
+//
 public interface MediaSet {
 
     public interface MediaSetListener {
         public void onContentChanged();
     }
 
-    public int getSubMediaSetCount();
+    public int getMediaItemCount();
 
     public MediaItem getMediaItem(int index);
 
-    public int getMediaItemCount();
+    public int getSubMediaSetCount();
+
+    public MediaSet getSubMediaSet(int index);
 
     public int getTotalMediaItemCount();
 
     public String getTitle();
 
     public MediaItem[] getCoverMediaItems();
-
-    public MediaSet getSubMediaSet(int index);
 }
diff --git a/new3d/src/com/android/gallery3d/data/Utils.java b/new3d/src/com/android/gallery3d/data/Utils.java
index a33aa82..fca5249 100644
--- a/new3d/src/com/android/gallery3d/data/Utils.java
+++ b/new3d/src/com/android/gallery3d/data/Utils.java
@@ -13,9 +13,10 @@
 import android.util.Log;
 
 public class Utils {
-    private static final int UNCONSTRAINED = -1;
     private static final String TAG = "Utils";
 
+    public static final int UNCONSTRAINED = -1;
+
     /*
      * Compute the sample size as a function of minSideLength
      * and maxNumOfPixels.
@@ -25,10 +26,10 @@
      * tolerable in terms of memory usage.
      *
      * The function returns a sample size based on the constraints.
-     * Both size and minSideLength can be passed in as IImage.UNCONSTRAINED,
+     * Both size and minSideLength can be passed in as UNCONSTRAINED,
      * which indicates no care of the corresponding constraint.
      * The functions prefers returning a sample size that
-     * generates a smaller bitmap, unless minSideLength = IImage.UNCONSTRAINED.
+     * generates a smaller bitmap, unless minSideLength = UNCONSTRAINED.
      *
      * Also, the function rounds up the sample size to a power of 2 or multiple
      * of 8 because BitmapFactory only honors sample size this way.
diff --git a/new3d/src/com/android/gallery3d/ui/OverlayLayout.java b/new3d/src/com/android/gallery3d/ui/OverlayLayout.java
deleted file mode 100644
index ec691d5..0000000
--- a/new3d/src/com/android/gallery3d/ui/OverlayLayout.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.android.gallery3d.ui;
-
-public class OverlayLayout extends GLView {
-
-    @Override
-    protected void onLayout(
-            boolean changed, int left, int top, int right, int bottom) {
-        int width = right - left;
-        int height = bottom - top;
-        for (int i = 0, n = getComponentCount(); i < n; ++i) {
-            GLView component = getComponent(i);
-            component.layout(0, 0, width, height);
-        }
-    }
-}
diff --git a/new3d/src/com/android/gallery3d/ui/TaggleButton.java b/new3d/src/com/android/gallery3d/ui/TaggleButton.java
deleted file mode 100644
index b703c51..0000000
--- a/new3d/src/com/android/gallery3d/ui/TaggleButton.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.android.gallery3d.ui;
-
-public class TaggleButton {
-
-}
diff --git a/new3d/tests/Android.mk b/new3d/tests/Android.mk
new file mode 100644
index 0000000..48d3f78
--- /dev/null
+++ b/new3d/tests/Android.mk
@@ -0,0 +1,17 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := GalleryNew3DTests
+LOCAL_CERTIFICATE := media
+
+LOCAL_INSTRUMENTATION_FOR := GalleryNew3D
+
+include $(BUILD_PACKAGE)
diff --git a/new3d/tests/AndroidManifest.xml b/new3d/tests/AndroidManifest.xml
new file mode 100644
index 0000000..0104295
--- /dev/null
+++ b/new3d/tests/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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>
+        <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."/>
+</manifest>
diff --git a/new3d/tests/src/com/android/gallery3d/anim/AnimationTest.java b/new3d/tests/src/com/android/gallery3d/anim/AnimationTest.java
new file mode 100644
index 0000000..9e919cf
--- /dev/null
+++ b/new3d/tests/src/com/android/gallery3d/anim/AnimationTest.java
@@ -0,0 +1,83 @@
+package com.android.gallery3d.anim;
+
+import android.util.Log;
+import android.view.animation.Interpolator;
+
+import junit.framework.TestCase;
+
+public class AnimationTest extends TestCase {
+    private static final String TAG = "AnimationTest";
+
+    public void testIntAnimation() {
+        IntAnimation a = new IntAnimation(0, 100, 10);  // value 0 to 100, 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
+        assertTrue(a.get() == 10); //
+        a.calculate(5);            // calculate value for time 5
+        assertTrue(a.get() == 50); //
+        a.calculate(9);            // calculate value for time 9
+        assertTrue(a.get() == 90); //
+        a.calculate(10);           // calculate value for time 10
+        assertTrue(!a.isActive()); // should be inactive now
+        assertTrue(a.get() == 100);//
+        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
+        assertTrue(a.get() == 90);
+    }
+
+    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);  //
+        // These are broken now, waiting for the bug fix.
+        //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 a, float b) {
+        assertTrue(Math.abs(a-b) < 1e-6);
+    }
+}
diff --git a/new3d/tests/src/com/android/gallery3d/data/LocalMediaSetTest.java b/new3d/tests/src/com/android/gallery3d/data/LocalMediaSetTest.java
new file mode 100644
index 0000000..50887c8
--- /dev/null
+++ b/new3d/tests/src/com/android/gallery3d/data/LocalMediaSetTest.java
@@ -0,0 +1,164 @@
+package com.android.gallery3d.data;
+
+import android.content.ContentResolver;
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+public class LocalMediaSetTest extends TestCase {
+    private static final String TAG = "LocalMediaSetTest";
+
+    public void testEmptySet() {
+        LocalMediaSet s = new LocalMediaSet(42, "Empty Set");
+        assertTrue(s.getMediaItemCount() == 0);
+        assertTrue(s.getSubMediaSetCount() == 0);
+        assertTrue(s.getTotalMediaItemCount() == 0);
+        assertTrue(s.getTitle().equals("Empty Set"));
+        assertTrue(s.getCoverMediaItems().length == 0);
+        assertTrue(s.getSubMediaSetById(42) == null);
+    }
+
+    private static class MyMediaItem implements MediaItem {
+        public String getMediaUri() { return ""; }
+        public String getTitle() { return ""; }
+        public Bitmap getImage(ContentResolver cr, int type) { return null; }
+        public void setListener(MediaItemListener listener) {}
+    }
+
+    public void testOneItem() {
+        LocalMediaSet s = new LocalMediaSet(1, "One Item Set");
+        MediaItem item = new MyMediaItem();
+        s.addMediaItem(item);
+        assertTrue(s.getMediaItemCount() == 1);
+        assertTrue(s.getSubMediaSetCount() == 0);
+        assertTrue(s.getTotalMediaItemCount() == 1);
+        assertTrue(s.getCoverMediaItems()[0] == item);
+        assertTrue(s.getMediaItem(0) == item);
+    }
+
+    public void testTwoItems() {
+        LocalMediaSet s = new LocalMediaSet(2, "Two Items Set");
+        MediaItem item1 = new MyMediaItem();
+        MediaItem item2 = new MyMediaItem();
+        s.addMediaItem(item1);
+        s.addMediaItem(item2);
+        assertTrue(s.getMediaItemCount() == 2);
+        assertTrue(s.getSubMediaSetCount() == 0);
+        assertTrue(s.getTotalMediaItemCount() == 2);
+        assertTrue(s.getCoverMediaItems()[0] == item1
+                || s.getCoverMediaItems()[0] == item2);
+    }
+
+    public void testEmptySubMediaSet() {
+        LocalMediaSet s = new LocalMediaSet(3, "One Empty Sub-MediaSet");
+        LocalMediaSet t = new LocalMediaSet(42, "Empty Set");
+        s.addSubMediaSet(t);
+        assertTrue(s.getMediaItemCount() == 0);
+        assertTrue(s.getSubMediaSetCount() == 1);
+        assertTrue(s.getTotalMediaItemCount() == 0);
+        assertTrue(s.getTitle().equals("One Empty Sub-MediaSet"));
+        assertTrue(s.getCoverMediaItems().length == 0);
+        assertTrue(s.getSubMediaSet(0) == t);
+        assertTrue(s.getSubMediaSetById(42) == t);
+        assertTrue(s.getSubMediaSetById(0) == null);
+        assertTrue(t.getTitle().equals("Empty Set"));
+    }
+
+    public void testSubSubMediaSet() {
+        LocalMediaSet s = new LocalMediaSet(0, "Set 0");
+        LocalMediaSet s1 = new LocalMediaSet(1, "Set 1");
+        LocalMediaSet s2 = new LocalMediaSet(2, "Set 2");
+        MediaItem item = new MyMediaItem();
+        s.addSubMediaSet(s1);
+        assertTrue(s.getMediaItemCount() == 0);
+        assertTrue(s.getSubMediaSetCount() == 1);
+        assertTrue(s.getTotalMediaItemCount() == 0);
+        assertTrue(s.getCoverMediaItems().length == 0);
+        assertTrue(s.getSubMediaSet(0) == s1);
+        assertTrue(s.getSubMediaSetById(0) == null);
+        assertTrue(s.getSubMediaSetById(1) == s1);
+        assertTrue(s.getSubMediaSetById(2) == null);
+        s1.addSubMediaSet(s2);
+        assertTrue(s.getMediaItemCount() == 0);
+        assertTrue(s.getSubMediaSetCount() == 1);
+        assertTrue(s.getTotalMediaItemCount() == 0);
+        assertTrue(s.getCoverMediaItems().length == 0);
+        assertTrue(s.getSubMediaSet(0) == s1);
+        assertTrue(s.getSubMediaSetById(0) == null);
+        assertTrue(s.getSubMediaSetById(1) == s1);
+        assertTrue(s.getSubMediaSetById(2) == null);
+        assertTrue(s1.getSubMediaSet(0) == s2);
+        assertTrue(s1.getSubMediaSetById(2) == s2);
+        s2.addMediaItem(item);
+        assertTrue(s.getMediaItemCount() == 0);
+        assertTrue(s.getSubMediaSetCount() == 1);
+        assertTrue(s.getTotalMediaItemCount() == 1);
+        assertTrue(s.getCoverMediaItems().length == 1);
+        assertTrue(s.getSubMediaSet(0) == s1);
+        assertTrue(s.getSubMediaSetById(0) == null);
+        assertTrue(s.getSubMediaSetById(1) == s1);
+        assertTrue(s.getSubMediaSetById(2) == null);
+    }
+
+    //
+    // [0] - [1]
+    //     -  2
+    //     -  3
+    //     - [4] -  5
+    //           -  6
+    //           -  7
+    //           - [8] - [9] - 10
+    //                 - [11]
+    //
+    public void testMediaSetTree() {
+        LocalMediaSet s0 = new LocalMediaSet(LocalMediaSet.ROOT_SET_ID, "Set 0");
+        LocalMediaSet s1 = new LocalMediaSet(1, "Set 1");
+        LocalMediaSet s4 = new LocalMediaSet(4, "Set 4");
+        LocalMediaSet s8 = new LocalMediaSet(8, "Set 8");
+        LocalMediaSet s9 = new LocalMediaSet(9, "Set 9");
+        LocalMediaSet s11 = new LocalMediaSet(11, "Set 11");
+        MediaItem t2 = new MyMediaItem();
+        MediaItem t3 = new MyMediaItem();
+        MediaItem t5 = new MyMediaItem();
+        MediaItem t6 = new MyMediaItem();
+        MediaItem t7 = new MyMediaItem();
+        MediaItem t10 = new MyMediaItem();
+
+        s0.addSubMediaSet(s1);
+        s0.addMediaItem(t2);
+        s0.addMediaItem(t3);
+        s0.addSubMediaSet(s4);
+        s4.addMediaItem(t5);
+        s4.addMediaItem(t6);
+        s4.addMediaItem(t7);
+        s4.addSubMediaSet(s8);
+        s8.addSubMediaSet(s9);
+        s9.addMediaItem(t10);
+        s8.addSubMediaSet(s11);
+
+        LocalMediaSet s = s0;
+
+        assertTrue(s.getMediaItemCount() == 2);
+        assertTrue(s.getSubMediaSetCount() == 2);
+        assertTrue(s.getTotalMediaItemCount() == 6);
+        assertTrue(s.getCoverMediaItems().length > 0);
+        assertTrue(s.getSubMediaSet(0) == s1);
+        assertTrue(s.getSubMediaSet(1) == s4);
+        assertTrue(s.getSubMediaSetById(1) == s1);
+        assertTrue(s.getSubMediaSetById(4) == s4);
+        assertTrue(s.getSubMediaSetById(8) == null);
+        assertTrue(s4.getSubMediaSetById(8) == s8);
+        assertTrue(s.getSubMediaSetById(LocalMediaSet.ROOT_SET_ID) == null);
+
+        MediaItem[] m = s.getCoverMediaItems();
+        for (int i = 0; i < m.length; i++) {
+            assertTrue(m[i] == t2 || m[i] == t3 || m[i] == t5
+                    || m[i] == t6 || m[i] == t7 || m[i] == t10);
+            for (int j = 0; j < i; j++) {
+                assertTrue(m[j] != m[i]);
+            }
+        }
+    }
+
+}
diff --git a/new3d/tests/src/com/android/gallery3d/data/UtilsTest.java b/new3d/tests/src/com/android/gallery3d/data/UtilsTest.java
new file mode 100644
index 0000000..db9499b
--- /dev/null
+++ b/new3d/tests/src/com/android/gallery3d/data/UtilsTest.java
@@ -0,0 +1,106 @@
+package com.android.gallery3d.data;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory.Options;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.util.Log;
+
+import java.util.Arrays;
+import junit.framework.TestCase;
+
+public class UtilsTest extends TestCase {
+    private static final String TAG = "UtilsTest";
+
+    private static final int [] testData = new int [] {
+        /* outWidth, outHeight, minSideLength, maxNumOfPixels, sample size */
+        0, 0, 0, 0, 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, Utils.UNCONSTRAINED, 1,  // 1
+        640, 480, 240, Utils.UNCONSTRAINED, 2,  // 2
+        640, 480, 160, Utils.UNCONSTRAINED, 4,  // 3->4
+        640, 480, 120, Utils.UNCONSTRAINED, 4,  // 4
+        640, 480, 96, Utils.UNCONSTRAINED,  8,  // 5->8
+        640, 480, 80, Utils.UNCONSTRAINED,  8,  // 6->8
+        640, 480, 60, Utils.UNCONSTRAINED,  8,  // 8
+        640, 480, 48, Utils.UNCONSTRAINED, 16,  // 10->16
+        640, 480, 40, Utils.UNCONSTRAINED, 16,  // 12->16
+        640, 480, 30, Utils.UNCONSTRAINED, 16,  // 16
+        640, 480, 24, Utils.UNCONSTRAINED, 24,  // 20->24
+        640, 480, 20, Utils.UNCONSTRAINED, 24,  // 24
+        640, 480, 16, Utils.UNCONSTRAINED, 32,  // 30->32
+        640, 480, 12, Utils.UNCONSTRAINED, 40,  // 40
+        640, 480, 10, Utils.UNCONSTRAINED, 48,  // 48
+        640, 480, 8, Utils.UNCONSTRAINED,  64,  // 60->64
+        640, 480, 6, Utils.UNCONSTRAINED,  80,  // 80
+        640, 480, 4, Utils.UNCONSTRAINED, 120,  // 120
+        640, 480, 3, Utils.UNCONSTRAINED, 160,  // 160
+        640, 480, 2, Utils.UNCONSTRAINED, 240,  // 240
+        640, 480, 1, Utils.UNCONSTRAINED, 480,  // 480
+
+        640, 480, Utils.UNCONSTRAINED, Utils.UNCONSTRAINED, 1,
+        640, 480, Utils.UNCONSTRAINED, 640 * 480, 1,                  // 1
+        640, 480, Utils.UNCONSTRAINED, 640 * 480 - 1, 2,              // a bit less than 1
+        640, 480, Utils.UNCONSTRAINED, 640 * 480 / 4, 2,              // 2
+        640, 480, Utils.UNCONSTRAINED, 640 * 480 / 4 - 1, 4,          // a bit less than 2
+        640, 480, Utils.UNCONSTRAINED, 640 * 480 / 9, 4,              // 3
+        640, 480, Utils.UNCONSTRAINED, 640 * 480 / 9 - 1, 4,          // a bit less than 3
+        640, 480, Utils.UNCONSTRAINED, 640 * 480 / 16, 4,             // 4
+        640, 480, Utils.UNCONSTRAINED, 640 * 480 / 16 - 1, 8,         // a bit less than 4
+        640, 480, Utils.UNCONSTRAINED, 640 * 480 / 64, 8,             // 8
+        640, 480, Utils.UNCONSTRAINED, 640 * 480 / 64 - 1, 16,        // a bit less than 8
+        640, 480, Utils.UNCONSTRAINED, 640 * 480 / 256, 16,           // 16
+        640, 480, Utils.UNCONSTRAINED, 640 * 480 / 256 - 1, 24,       // a bit less than 16
+        640, 480, Utils.UNCONSTRAINED, 640 * 480 / (24 * 24) - 1, 32, // a bit less than 24
+    };
+
+    public void testComputeSampleSize() {
+        Options options = new Options();
+
+        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];
+            options.outWidth = w;
+            options.outHeight = h;
+            int result = Utils.computeSampleSize(options, minSide, maxPixels);
+            if (result != sampleSize) {
+                Log.v(TAG, w + "x" + h + ", minSide = " + minSide + ", maxPixels = "
+                        + maxPixels + ", sampleSize = " + sampleSize + ", result = "
+                        + result);
+            }
+            assertTrue(sampleSize == result);
+        }
+    }
+}