Merge "Test Aspect Ratio in Different Orientations" into ics-mr1
diff --git a/tests/tests/animation/src/android/animation/cts/AnimationActivity.java b/tests/tests/animation/src/android/animation/cts/AnimationActivity.java
index ee3202e..43679c8 100644
--- a/tests/tests/animation/src/android/animation/cts/AnimationActivity.java
+++ b/tests/tests/animation/src/android/animation/cts/AnimationActivity.java
@@ -58,7 +58,6 @@
     float mDeltaY = 200f;
     long mDuration = 1000;
     public AnimationView view = null;
-    private boolean isAnimationRunning = false;
 
     @Override
     public void onCreate(Bundle bundle){
@@ -92,12 +91,12 @@
     }
 
     public ValueAnimator createAnimatorWithRepeatMode(int repeatMode) {
-        return createAnimator(view.newBall, "y", 1000,ValueAnimator.INFINITE, repeatMode,
+        return createAnimator(view.newBall, "y", 1000, ValueAnimator.INFINITE, repeatMode,
                 new AccelerateInterpolator(), mStartY, mStartY + mDeltaY);
     }
 
     public ValueAnimator createAnimatorWithRepeatCount(int repeatCount) {
-        return createAnimator(view.newBall, "y", 1000,repeatCount, ValueAnimator.REVERSE,
+        return createAnimator(view.newBall, "y", 1000, repeatCount, ValueAnimator.REVERSE,
                 new AccelerateInterpolator(), mStartY, mStartY + mDeltaY);
     }
 
@@ -106,46 +105,98 @@
                 ValueAnimator.REVERSE,new AccelerateInterpolator(), mStartY, mStartY + mDeltaY);
     }
 
+    public ValueAnimator createAnimatorWithInterpolator(TimeInterpolator interpolator){
+        return createAnimator(view.newBall, "y", 1000, ValueAnimator.INFINITE, ValueAnimator.REVERSE,
+                interpolator, mStartY, mStartY + mDeltaY);
+    }
+
+    public ValueAnimator createObjectAnimatorForInt(Object object,String propertyName,
+            long duration, int repeatCount, int repeatMode, TimeInterpolator timeInterpolator,
+            int start, int end) {
+        ObjectAnimator objAnimator = ObjectAnimator.ofInt(object, propertyName, start,end);
+        objAnimator.setDuration(duration);
+
+        objAnimator.setRepeatCount(repeatCount);
+        objAnimator.setInterpolator(timeInterpolator);
+        objAnimator.setRepeatMode(repeatMode);
+        return objAnimator;
+    }
+
+    public ValueAnimator createObjectAnimatorForInt() {
+        ObjectAnimator objAnimator = ObjectAnimator.ofInt(view.newBall, "y", (int)mStartY,
+            (int)(mStartY + mDeltaY));
+        objAnimator.setDuration(mDuration);
+
+        objAnimator.setRepeatCount(ValueAnimator.INFINITE);
+        objAnimator.setInterpolator(new AccelerateInterpolator());
+        objAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        return objAnimator;
+    }
+
     public void startAnimation(long duration){
         ValueAnimator bounceAnimator = ObjectAnimator.ofFloat(view.newBall, "y", mStartY,
-                mStartY + mDeltaY);
+            mStartY + mDeltaY);
         bounceAnimator.setDuration(duration);
         bounceAnimator.setRepeatCount(ValueAnimator.INFINITE);
         bounceAnimator.setInterpolator(new AccelerateInterpolator());
         bounceAnimator.setRepeatMode(ValueAnimator.REVERSE);
         view.bounceAnimator = bounceAnimator;
+        view.startColorAnimator();
         view.animateBall();
     }
 
     public void startAnimation(ValueAnimator valueAnimator){
         view.bounceAnimator = valueAnimator;
+        view.startColorAnimator();
         view.animateBall();
-        isAnimationRunning = true;
+    }
+
+    public void startAnimation(ObjectAnimator bounceAnimator) {
+        view.bounceAnimator = bounceAnimator;
+        view.startColorAnimator();
+        view.animateBall();
+    }
+
+    public void startAnimation(ObjectAnimator bounceAnimator, ObjectAnimator colorAnimator) {
+        view.bounceAnimator = bounceAnimator;
+        view.startColorAnimator(colorAnimator);
+        view.animateBall();
+    }
+
+    public void startColorAnimation(ValueAnimator colorAnimator){
+        view.startColorAnimator(colorAnimator);
     }
 
     public class AnimationView extends View {
-        private static final int RED = 0xffFF8080;
-        private static final int BLUE = 0xff8080FF;
+        public static final int RED = 0xffFF8080;
+        public static final int BLUE = 0xff8080FF;
         public ShapeHolder newBall = null;
         public final ArrayList<ShapeHolder> balls = new ArrayList<ShapeHolder>();
         AnimatorSet animation = null;
         public ValueAnimator bounceAnimator;
+        public ValueAnimator colorAnimator;
 
         public AnimationView(Context context) {
             super(context);
-            ValueAnimator colorAnim = ObjectAnimator.ofInt(this, "backgroundColor", RED, BLUE);
-            colorAnim.setDuration(1000);
-            colorAnim.setEvaluator(new ArgbEvaluator());
-            colorAnim.setRepeatCount(1);
-            colorAnim.setRepeatMode(ValueAnimator.REVERSE);
-            colorAnim.start();
             newBall = addBall(mBallHeight, mBallWidth);
         }
 
+        public void startColorAnimator() {
+            colorAnimator = ObjectAnimator.ofInt(this, "backgroundColor", RED, BLUE);
+            colorAnimator.setDuration(1000);
+            colorAnimator.setEvaluator(new ArgbEvaluator());
+            colorAnimator.setRepeatCount(ValueAnimator.INFINITE);
+            colorAnimator.setRepeatMode(ValueAnimator.REVERSE);
+            colorAnimator.start();
+        }
+
+        public void startColorAnimator(ValueAnimator animator) {
+            this.colorAnimator = animator;
+            colorAnimator.start();
+        }
+
         @Override
         public boolean onTouchEvent(MotionEvent event) {
-
-
             return true;
         }
 
@@ -203,4 +254,3 @@
         }
     }
 }
-
diff --git a/tests/tests/animation/src/android/animation/cts/ArgbEvaluatorTest.java b/tests/tests/animation/src/android/animation/cts/ArgbEvaluatorTest.java
new file mode 100644
index 0000000..33c3716
--- /dev/null
+++ b/tests/tests/animation/src/android/animation/cts/ArgbEvaluatorTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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 android.animation.cts;
+
+import android.animation.ArgbEvaluator;
+import android.graphics.Color;
+import android.test.InstrumentationTestCase;
+
+public class ArgbEvaluatorTest extends InstrumentationTestCase {
+    public void testEvaluate() throws Throwable {
+        final int RED =  0xffFF8080;
+        final int BLUE = 0xff8080FF;
+        int aRED = Color.alpha(RED);
+        int rRED = Color.red(RED);
+        int gRED = Color.green(RED);
+        int bRED = Color.blue(RED);
+        int aBLUE = Color.alpha(BLUE);
+        int rBLUE = Color.red(BLUE);
+        int gBLUE = Color.green(BLUE);
+        int bBLUE = Color.blue(BLUE);
+
+        final ArgbEvaluator evaluator = new ArgbEvaluator();
+        class AnimationRunnable implements Runnable{
+            int result;
+            public void run() {
+                result = (Integer) evaluator.evaluate(0.5f, RED, BLUE);
+            }
+        }
+        AnimationRunnable aRunnable = new AnimationRunnable();
+        this.runTestOnUiThread(aRunnable);
+        Thread.sleep(100);
+        int result = aRunnable.result;
+
+        int aResult = Color.alpha(result);
+        int rResult = Color.red(result);
+        int gResult = Color.green(result);
+        int bResult = Color.blue(result);
+        assertTrue(aResult >= aRED);
+        assertTrue(aResult <= aBLUE);
+        assertTrue(rResult <= rRED);
+        assertTrue(rResult >= rBLUE);
+        assertTrue(gResult >= gRED);
+        assertTrue(gResult <= gBLUE);
+        assertTrue(bResult >= bRED);
+        assertTrue(bResult <= bBLUE);
+    }
+}
+
diff --git a/tests/tests/animation/src/android/animation/cts/FloatEvaluatorTest.java b/tests/tests/animation/src/android/animation/cts/FloatEvaluatorTest.java
new file mode 100644
index 0000000..8cc1ffe
--- /dev/null
+++ b/tests/tests/animation/src/android/animation/cts/FloatEvaluatorTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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 android.animation.cts;
+
+import android.animation.FloatEvaluator;
+import android.test.InstrumentationTestCase;
+
+public class FloatEvaluatorTest extends InstrumentationTestCase {
+    public void testEvaluate() {
+        float start = 0.0f;
+        float end = 1.0f;
+        float fraction = 0.5f;
+        FloatEvaluator floatEvaluator = new FloatEvaluator();
+        float result = floatEvaluator.evaluate(fraction, start, end);
+        assertTrue(result >= (fraction*start));
+        assertTrue(result <= (fraction*end));
+    }
+}
+
diff --git a/tests/tests/animation/src/android/animation/cts/IntEvaluatorTest.java b/tests/tests/animation/src/android/animation/cts/IntEvaluatorTest.java
new file mode 100644
index 0000000..11df20c
--- /dev/null
+++ b/tests/tests/animation/src/android/animation/cts/IntEvaluatorTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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 android.animation.cts;
+
+import android.animation.IntEvaluator;
+import android.test.InstrumentationTestCase;
+
+public class IntEvaluatorTest extends InstrumentationTestCase {
+    public void testEvaluate() throws Throwable {
+        final int start = 0;
+        final int end = 100;
+        final float fraction = 0.5f;
+        final IntEvaluator intEvaluator = new IntEvaluator();
+        class AnimationRunnable implements Runnable{
+            int result;
+            public void run() {
+                result = intEvaluator.evaluate(fraction, start, end);
+            }
+        }
+        AnimationRunnable aRunnable = new AnimationRunnable();
+        this.runTestOnUiThread(aRunnable);
+        Thread.sleep(1);
+        int result = aRunnable.result;
+        assertTrue(result >= (fraction*start));
+        assertTrue(result <= (fraction*end));
+    }
+}
+
diff --git a/tests/tests/animation/src/android/animation/cts/ObjectAnimatorTest.java b/tests/tests/animation/src/android/animation/cts/ObjectAnimatorTest.java
new file mode 100644
index 0000000..be5c1d4
--- /dev/null
+++ b/tests/tests/animation/src/android/animation/cts/ObjectAnimatorTest.java
@@ -0,0 +1,269 @@
+/*
+ * 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 android.animation.cts;
+
+import android.animation.ArgbEvaluator;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Property;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.Interpolator;
+
+public class ObjectAnimatorTest extends
+        ActivityInstrumentationTestCase2<AnimationActivity> {
+    private AnimationActivity mActivity;
+    private ObjectAnimator mObjectAnimator;
+    private long mDuration = 1000;
+
+    public ObjectAnimatorTest() {
+        super(AnimationActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        setActivityInitialTouchMode(false);
+        mActivity = getActivity();
+        mObjectAnimator = (ObjectAnimator) mActivity.createAnimatorWithDuration(mDuration);
+    }
+
+    public void testDuration() throws Throwable {
+        final long duration = 2000;
+        ObjectAnimator objectAnimatorLocal = (ObjectAnimator)mActivity.createAnimatorWithDuration(
+            duration);
+        startAnimation(objectAnimatorLocal);
+        assertEquals(duration, objectAnimatorLocal.getDuration());
+    }
+    public void testOfFloat() throws Throwable {
+        Object object = mActivity.view.newBall;
+        String property = "y";
+        float startY = mActivity.mStartY;
+        float endY = mActivity.mStartY + mActivity.mDeltaY;
+        ObjectAnimator objAnimator = ObjectAnimator.ofFloat(object, property, startY, endY);
+        assertTrue(objAnimator != null);
+        objAnimator.setDuration(mDuration);
+        objAnimator.setRepeatCount(ValueAnimator.INFINITE);
+        objAnimator.setInterpolator(new AccelerateInterpolator());
+        objAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        startAnimation(objAnimator);
+        assertTrue(objAnimator != null);
+        Thread.sleep(100);
+        float x = mActivity.view.newBall.getX();
+        float y = mActivity.view.newBall.getY();
+        assertTrue( y >= startY);
+        assertTrue( y <= endY);
+    }
+
+    public void testOfFloatBase() throws Throwable {
+        Object object = mActivity.view.newBall;
+        String property = "y";
+        float startY = mActivity.mStartY;
+        float endY = mActivity.mStartY + mActivity.mDeltaY;
+        ObjectAnimator animator = ObjectAnimator.ofFloat(object, property, startY, endY);
+        ObjectAnimator objAnimator = new ObjectAnimator();
+        objAnimator.setTarget(object);
+        objAnimator.setPropertyName(property);
+        assertEquals(animator.getTarget(), objAnimator.getTarget());
+        assertEquals(animator.getPropertyName(), objAnimator.getPropertyName());
+    }
+
+    public void testOfInt() throws Throwable {
+        Object object = mActivity.view.newBall;
+        String property = "backgroundColor";
+        int startColor = mActivity.view.RED;
+        int endColor = mActivity.view.BLUE;
+
+        ObjectAnimator colorAnimator = ObjectAnimator.ofInt(object, property,
+                startColor, endColor);
+        colorAnimator.setDuration(1000);
+        colorAnimator.setEvaluator(new ArgbEvaluator());
+        colorAnimator.setRepeatCount(1);
+        colorAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        colorAnimator.start();
+        startAnimation(mObjectAnimator, colorAnimator);
+        Thread.sleep(100);
+        Integer i = (Integer) colorAnimator.getAnimatedValue();
+        //We are going from less negative value to a more negative value
+        assertTrue(i.intValue() <= startColor);
+        assertTrue(endColor <= i.intValue());
+    }
+
+    public void testOfObject() throws Throwable {
+        Object object = mActivity.view.newBall;
+        String property = "backgroundColor";
+        int startColor = mActivity.view.RED;
+        int endColor = mActivity.view.BLUE;
+        Object[] values = {new Integer(startColor), new Integer(endColor)};
+        ArgbEvaluator evaluator = new ArgbEvaluator();
+        ObjectAnimator colorAnimator = ObjectAnimator.ofObject(object, property,
+                evaluator, values);
+        colorAnimator.setDuration(1000);
+        colorAnimator.setRepeatCount(1);
+        colorAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        colorAnimator.start();
+        startAnimation(mObjectAnimator, colorAnimator);
+        Thread.sleep(100);
+        Integer i = (Integer) colorAnimator.getAnimatedValue();
+        //We are going from less negative value to a more negative value
+        assertTrue(i.intValue() <= startColor);
+        assertTrue(endColor <= i.intValue());
+    }
+
+    public void testOfPropertyValuesHolder() throws Throwable {
+        Object object = mActivity.view.newBall;
+        String propertyName = "backgroundColor";
+        int startColor = mActivity.view.RED;
+        int endColor = mActivity.view.BLUE;
+        int values[] = {startColor, endColor};
+        ArgbEvaluator evaluator = new ArgbEvaluator();
+        PropertyValuesHolder propertyValuesHolder = PropertyValuesHolder.ofInt(propertyName, values);
+        ObjectAnimator colorAnimator = ObjectAnimator.ofPropertyValuesHolder(object, 
+            propertyValuesHolder);
+        colorAnimator.setDuration(1000);
+        colorAnimator.setRepeatCount(1);
+        colorAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        colorAnimator.start();
+        startAnimation(mObjectAnimator, colorAnimator);
+        Thread.sleep(100);
+        Integer i = (Integer) colorAnimator.getAnimatedValue();
+        //We are going from less negative value to a more negative value
+        assertTrue(i.intValue() <= startColor);
+        assertTrue(endColor <= i.intValue());
+    }
+
+    public void testGetPropertyName() throws Throwable {
+        Object object = mActivity.view.newBall;
+        String propertyName = "backgroundColor";
+        int startColor = mActivity.view.RED;
+        int endColor = mActivity.view.BLUE;
+        Object[] values = {new Integer(startColor), new Integer(endColor)};
+        ArgbEvaluator evaluator = new ArgbEvaluator();
+        ObjectAnimator colorAnimator = ObjectAnimator.ofObject(object, propertyName,
+                evaluator, values);
+        String actualPropertyName = colorAnimator.getPropertyName();
+        assertEquals(propertyName, actualPropertyName);
+    }
+
+    public void testSetCurrentPlayTime() throws Throwable {
+        Object object = mActivity.view.newBall;
+        String propertyName = "backgroundColor";
+        int startColor = mActivity.view.RED;
+        int endColor = mActivity.view.BLUE;
+        long playTime = 10000l;
+        Object[] values = {new Integer(startColor), new Integer(endColor)};
+        ArgbEvaluator evaluator = new ArgbEvaluator();
+        ObjectAnimator colorAnimator = ObjectAnimator.ofObject(object, propertyName,
+                evaluator, values);
+        colorAnimator.setCurrentPlayTime(playTime);
+        long actualPlayTime = colorAnimator.getCurrentPlayTime();
+        assertEquals(playTime, actualPlayTime);
+    }
+
+    public void testSetFloatValues() throws Throwable {
+        Object object = mActivity.view.newBall;
+        String property = "y";
+        float startY = mActivity.mStartY;
+        float endY = mActivity.mStartY + mActivity.mDeltaY;
+        float[] values = {startY, endY};
+        ObjectAnimator objAnimator = new ObjectAnimator();
+        objAnimator.setTarget(object);
+        objAnimator.setPropertyName(property);
+        objAnimator.setFloatValues(values);
+        objAnimator.setDuration(mDuration);
+        objAnimator.setRepeatCount(ValueAnimator.INFINITE);
+        objAnimator.setInterpolator(new AccelerateInterpolator());
+        objAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        startAnimation(objAnimator);
+        Thread.sleep(100);
+        float y = mActivity.view.newBall.getY();
+        assertTrue( y >= startY);
+        assertTrue( y <= endY);
+    }
+
+    public void testGetTarget() throws Throwable {
+        Object object = mActivity.view.newBall;
+        String propertyName = "backgroundColor";
+        int startColor = mActivity.view.RED;
+        int endColor = mActivity.view.BLUE;
+        Object[] values = {new Integer(startColor), new Integer(endColor)};
+        ArgbEvaluator evaluator = new ArgbEvaluator();
+        ObjectAnimator colorAnimator = ObjectAnimator.ofObject(object, propertyName,
+                evaluator, values);
+        Object target = colorAnimator.getTarget();
+        assertEquals(object, target);
+    }
+
+    public void testClone() throws Throwable {
+        Object object = mActivity.view.newBall;
+        String property = "y";
+        float startY = mActivity.mStartY;
+        float endY = mActivity.mStartY + mActivity.mDeltaY;
+        Interpolator interpolator = new AccelerateInterpolator();
+        ObjectAnimator objAnimator = ObjectAnimator.ofFloat(object, property, startY, endY);
+        objAnimator.setDuration(mDuration);
+        objAnimator.setRepeatCount(ValueAnimator.INFINITE);
+        objAnimator.setInterpolator(interpolator);
+        objAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        ObjectAnimator cloneAnimator = objAnimator.clone();
+
+        assertEquals(mDuration, cloneAnimator.getDuration());
+        assertEquals(ValueAnimator.INFINITE, cloneAnimator.getRepeatCount());
+        assertEquals(ValueAnimator.REVERSE, cloneAnimator.getRepeatMode());
+        assertEquals(object, cloneAnimator.getTarget());
+        assertEquals(property, cloneAnimator.getPropertyName());
+        assertEquals(interpolator, cloneAnimator.getInterpolator());
+    }
+
+    public void testIsStarted() throws Throwable {
+        Object object = mActivity.view.newBall;
+        String property = "y";
+        float startY = mActivity.mStartY;
+        float endY = mActivity.mStartY + mActivity.mDeltaY;
+        Interpolator interpolator = new AccelerateInterpolator();
+        ObjectAnimator objAnimator = ObjectAnimator.ofFloat(object, property, startY, endY);
+        objAnimator.setDuration(mDuration);
+        objAnimator.setRepeatCount(ValueAnimator.INFINITE);
+        objAnimator.setInterpolator(interpolator);
+        objAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        startAnimation(objAnimator);
+        Thread.sleep(100);
+        assertTrue(objAnimator.isStarted());
+        Thread.sleep(100);
+    }
+
+    private void startAnimation(final ObjectAnimator mObjectAnimator) throws Throwable {
+        Thread mAnimationRunnable = new Thread() {
+            public void run() {
+                mActivity.startAnimation(mObjectAnimator);
+            }
+        };
+        this.runTestOnUiThread(mAnimationRunnable);
+    }
+    private void startAnimation(final ObjectAnimator mObjectAnimator, final
+            ObjectAnimator colorAnimator) throws Throwable {
+        Thread mAnimationRunnable = new Thread() {
+            public void run() {
+                mActivity.startAnimation(mObjectAnimator, colorAnimator);
+            }
+        };
+        this.runTestOnUiThread(mAnimationRunnable);
+    }
+}
+
+
diff --git a/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java b/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java
index 314c06d..a7626bc 100644
--- a/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java
+++ b/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java
@@ -15,24 +15,22 @@
  */
 package android.animation.cts;
 
+import android.animation.ArgbEvaluator;
+import android.animation.FloatEvaluator;
+import android.animation.IntEvaluator;
+import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
-import android.app.Instrumentation;
-import android.content.Intent;
 import android.test.ActivityInstrumentationTestCase2;
+import android.view.animation.AccelerateInterpolator;
 
 public class ValueAnimatorTest extends
         ActivityInstrumentationTestCase2<AnimationActivity> {
     private AnimationActivity mActivity;
-    private Instrumentation mInstrumentation;
     private ValueAnimator mValueAnimator;
-    private long mDuration;
+    private long mDuration = 1000;
 
     public ValueAnimatorTest() {
-        super("com.android.cts.animation",AnimationActivity.class);
-    }
-
-    public ValueAnimatorTest(Class<AnimationActivity> activityClass) {
-        super("com.android.cts.animation",AnimationActivity.class);
+        super(AnimationActivity.class);
     }
 
     @Override
@@ -57,6 +55,16 @@
         assertTrue(valueAnimatorReturned.isRunning());
     }
 
+    public void testIsStarted() throws Throwable {
+        assertFalse(mValueAnimator.isRunning());
+        assertFalse(mValueAnimator.isStarted());
+        long startDelay = 10000;
+        mValueAnimator.setStartDelay(startDelay);
+        startAnimation(mValueAnimator);
+        assertFalse(mValueAnimator.isRunning());
+        assertTrue(mValueAnimator.isStarted());
+    }
+
     public void testRepeatMode() throws Throwable {
         ValueAnimator mValueAnimator = mActivity.createAnimatorWithRepeatMode(ValueAnimator.RESTART);
         startAnimation(mValueAnimator);
@@ -92,12 +100,164 @@
         assertEquals(frameDelay, actualFrameDelay);
     }
 
-    private void startAnimation(final ValueAnimator mValueAnimator) throws Throwable {
+    public void testSetInterpolator() throws Throwable {
+        AccelerateInterpolator interpolator = new AccelerateInterpolator();
+        ValueAnimator mValueAnimator = mActivity.createAnimatorWithInterpolator(interpolator);
+        startAnimation(mValueAnimator);
+        assertTrue(interpolator.equals(mValueAnimator.getInterpolator()));
+    }
+
+    public void testCancel() throws Throwable {
+        startAnimation(mValueAnimator);
+        Thread.sleep(100);
+        mValueAnimator.cancel();
+        assertFalse(mValueAnimator.isRunning());
+    }
+
+    public void testEnd() throws Throwable {
+        Object object = mActivity.view.newBall;
+        String property = "y";
+        float startY = mActivity.mStartY;
+        float endY = mActivity.mStartY + mActivity.mDeltaY;
+        ObjectAnimator objAnimator = ObjectAnimator.ofFloat(object, property, startY, endY);
+        objAnimator.setDuration(mDuration);
+        objAnimator.setRepeatCount(ValueAnimator.INFINITE);
+        objAnimator.setInterpolator(new AccelerateInterpolator());
+        objAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        startAnimation(objAnimator);
+        Thread.sleep(100);
+        endAnimation(objAnimator);
+        float y = mActivity.view.newBall.getY();
+        assertEquals(y, endY);
+    }
+
+    public void testGetAnimatedFraction() throws Throwable {
+        ValueAnimator objAnimator = getAnimator();
+        startAnimation(objAnimator);
+        assertNotNull(objAnimator);
+        float[] fractions = getValue(objAnimator, 10, "getAnimatedFraction()", 100l, null);
+        for(int j = 0; j < 9; j++){
+            assertTrue(fractions[j] >= 0.0);
+            assertTrue(fractions[j] <= 1.0);
+            assertTrue(fractions[j + 1] != fractions[j]);
+        }
+    }
+
+    public void testGetAnimatedValue() throws Throwable {
+        ValueAnimator objAnimator = getAnimator();
+        startAnimation(objAnimator);
+        assertNotNull(objAnimator);
+        float[] animatedValues = getValue(objAnimator, 10, "getAnimatedValue()", 100l, null);
+
+        for(int j = 0; j < 9; j++){
+            assertTrue(animatedValues[j + 1] != animatedValues[j]);
+        }
+    }
+    public void testGetAnimatedValue_PropertyName() throws Throwable {
+        String property = "y";
+
+        ValueAnimator objAnimator = getAnimator();
+        startAnimation(objAnimator);
+        assertNotNull(objAnimator);
+        float[] animatedValues = getValue(objAnimator, 10, "getAnimatedValue(property)", 100l,
+            property);
+        for(int j = 0; j < 9; j++){
+            assertTrue(animatedValues[j + 1] != animatedValues[j]);
+        }
+    }
+
+    public void testOfFloat() throws Throwable {
+        float start = 0.0f;
+        float end = 1.0f;
+        float[] values = {start, end};
+        final ValueAnimator valueAnimatorLocal = ValueAnimator.ofFloat(values);
+        valueAnimatorLocal.setDuration(mDuration);
+        valueAnimatorLocal.setRepeatCount(ValueAnimator.INFINITE);
+        valueAnimatorLocal.setInterpolator(new AccelerateInterpolator());
+        valueAnimatorLocal.setRepeatMode(ValueAnimator.RESTART);
+
         this.runTestOnUiThread(new Runnable(){
-            public void run(){
-                mActivity.startAnimation(mValueAnimator);
+            public void run() {
+                valueAnimatorLocal.start();
             }
         });
+        Thread.sleep(100);
+        boolean isRunning = valueAnimatorLocal.isRunning();
+        assertTrue(isRunning);
+
+        Float animatedValue = (Float) valueAnimatorLocal.getAnimatedValue();
+        assertTrue(animatedValue >= start);
+        assertTrue(animatedValue <= end);
+    }
+
+    public void testOfInt() throws Throwable {
+        int start = 0;
+        int end = 10;
+        int[] values = {start, end};
+        final ValueAnimator valueAnimatorLocal = ValueAnimator.ofInt(values);
+        valueAnimatorLocal.setDuration(mDuration);
+        valueAnimatorLocal.setRepeatCount(ValueAnimator.INFINITE);
+        valueAnimatorLocal.setInterpolator(new AccelerateInterpolator());
+        valueAnimatorLocal.setRepeatMode(ValueAnimator.RESTART);
+
+        this.runTestOnUiThread(new Runnable(){
+            public void run() {
+                valueAnimatorLocal.start();
+            }
+        });
+        Thread.sleep(100);
+        boolean isRunning = valueAnimatorLocal.isRunning();
+        assertTrue(isRunning);
+
+        Integer animatedValue = (Integer) valueAnimatorLocal.getAnimatedValue();
+        assertTrue(animatedValue >= start);
+        assertTrue(animatedValue <= end);
+    }
+
+    private ValueAnimator getAnimator() {
+        Object object = mActivity.view.newBall;
+        String property = "y";
+        float startY = mActivity.mStartY;
+        float endY = mActivity.mStartY + mActivity.mDeltaY;
+        ValueAnimator objAnimator = ObjectAnimator.ofFloat(object, property, startY, endY);
+        objAnimator.setDuration(mDuration);
+        objAnimator.setRepeatCount(ValueAnimator.INFINITE);
+        objAnimator.setInterpolator(new AccelerateInterpolator());
+        objAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        return objAnimator;
+    }
+
+    private float[] getValue(ValueAnimator animator, int n, String methodName, 
+            long sleepTime, String property) throws InterruptedException {
+        float[] values = new float[n];
+        for(int i = 0; i < (n-1); i++){
+            Thread.sleep(sleepTime);
+            float value = 0.0f;
+            if(methodName.equals("getAnimatedFraction()")) {
+                value = animator.getAnimatedFraction();
+            }else if(methodName.equals("getAnimatedValue()")) {
+              value = ((Float)animator.getAnimatedValue()).floatValue();
+            }else if(methodName.equals("getAnimatedValue(property)")) {
+              value = ((Float)animator.getAnimatedValue(property)).floatValue();
+            }
+            values[i] = value;
+        }
+        return values;
+    }
+    private void startAnimation(final ValueAnimator mValueAnimator) throws Throwable {
+        this.runTestOnUiThread(new Runnable(){
+            public void run() {
+                  mActivity.startAnimation(mValueAnimator);
+            }
+        });
+    }
+    private void endAnimation(final ValueAnimator mValueAnimator) throws Throwable {
+        Thread animationRunnable = new Thread() {
+            public void run() {
+                mValueAnimator.end();
+            }
+        };
+        this.runTestOnUiThread(animationRunnable);
     }
 }
 
diff --git a/tests/tests/os/src/android/os/cts/EnvironmentTest.java b/tests/tests/os/src/android/os/cts/EnvironmentTest.java
index 2081cba..711306e 100644
--- a/tests/tests/os/src/android/os/cts/EnvironmentTest.java
+++ b/tests/tests/os/src/android/os/cts/EnvironmentTest.java
@@ -70,4 +70,15 @@
         assertTrue(Environment.getDownloadCacheDirectory().isDirectory());
         assertTrue(Environment.getDataDirectory().isDirectory());
     }
+
+    /**
+     * TMPDIR being set prevents apps from asking to have temporary files
+     * placed in their own storage, instead forcing their location to
+     * something OS-defined. If TMPDIR points to a global shared directory,
+     * this could compromise the security of the files.
+     */
+    public void testNoTmpDir() {
+        assertNull("environment variable TMPDIR should not be set",
+                System.getenv("TMPDIR"));
+    }
 }
diff --git a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
index 2e3bbdb..c1defb3 100644
--- a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
@@ -212,12 +212,14 @@
                     "/data/data/com.htc.loggers/tmp",
                     "/data/data/com.htc.loggers/htcghost",
                     "/data/data/com.android.providers.drm/rights",
+                    "/data/data/recovery",
                     "/data/dontpanic",
                     "/data/drm",
                     "/data/drm/rights",
                     "/data/dump",
                     "/data/htcfs",
                     "/data/local",
+                    "/data/local/tmp",
                     "/data/local/tmp/com.nuance.android.vsuite.vsuiteapp",
                     "/data/log",
                     "/data/lost+found",
@@ -228,6 +230,7 @@
                     "/data/misc/wifi/sockets",
                     "/data/property",
                     "/data/secure",
+                    "/data/sensors",
                     "/data/shared",
                     "/data/system",
                     "/data/wifi",
diff --git a/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java b/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java
index b2bdfc8..a31e0c0 100644
--- a/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java
+++ b/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java
@@ -56,6 +56,12 @@
 
     public static final String EXT_WEB_URL1 = "http://www.example.com/";
 
+    public static final String LOCAL_FILESYSTEM_URL = "file:///etc/hosts";
+
+    // Must match the title of the page at
+    // android/frameworks/base/core/res/res/raw/loaderror.html
+    public static final String WEBPAGE_NOT_AVAILABLE_TITLE = "Webpage not available";
+
     public static final String getFileUrl(String assetName) {
         if (assetName.contains(":") || assetName.startsWith("/")) {
             throw new IllegalArgumentException();
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
index 639f795..05e668b 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
@@ -214,7 +214,6 @@
             args = {boolean.class}
         )
     })
-    @ToBeFixed(explanation = "Cannot block file access using setAllowFileAccess(false)")
     public void testAccessAllowFileAccess() {
         assertTrue(mSettings.getAllowFileAccess());
 
@@ -225,11 +224,15 @@
         fileUrl = TestHtmlConstants.getFileUrl(TestHtmlConstants.BR_TAG_URL);
         mSettings.setAllowFileAccess(false);
         assertFalse(mSettings.getAllowFileAccess());
+
         loadUrl(fileUrl);
-        // direct file:// access still works with access disabled
+        // android_asset URLs should still be loaded when even with file access
+        // disabled.
         assertEquals(TestHtmlConstants.BR_TAG_TITLE, mWebView.getTitle());
 
-        // ToBeFixed: How does this API prevent file access?
+        // Files on the file system should not be loaded.
+        loadUrl(TestHtmlConstants.LOCAL_FILESYSTEM_URL);
+        assertEquals(TestHtmlConstants.WEBPAGE_NOT_AVAILABLE_TITLE, mWebView.getTitle());
     }
 
     @TestTargets({
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
index 855c209..686c90a 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
@@ -84,6 +84,9 @@
     @Option(name = "quiet-output", description = "Mute display of test results.")
     private boolean mQuietOutput = false;
 
+    @Option(name = "result-server", description = "Server to publish test results.")
+    private String mResultServer;
+
     protected IBuildInfo mBuildInfo;
     private String mStartTime;
     private String mDeviceSerial;
@@ -254,9 +257,18 @@
             CLog.w("Unable to create XML report");
             return;
         }
-        createXmlResult(mReportDir, mStartTime, elapsedTime);
+
+        File reportFile = getResultFile(mReportDir);
+        createXmlResult(reportFile, mStartTime, elapsedTime);
         copyFormattingFiles(mReportDir);
         zipResults(mReportDir);
+
+        try {
+            ResultReporter reporter = new ResultReporter(mResultServer, reportFile);
+            reporter.reportResult();
+        } catch (IOException e) {
+            CLog.e(e);
+        }
     }
 
     private void logResult(String format, Object... args) {
@@ -281,12 +293,11 @@
     /**
      * Creates a report file and populates it with the report data from the completed tests.
      */
-    private void createXmlResult(File reportDir, String startTimestamp, long elapsedTime) {
+    private void createXmlResult(File reportFile, String startTimestamp, long elapsedTime) {
         String endTime = getTimestamp();
-
         OutputStream stream = null;
         try {
-            stream = createOutputResultStream(reportDir);
+            stream = createOutputResultStream(reportFile);
             KXmlSerializer serializer = new KXmlSerializer();
             serializer.setOutput(stream, "UTF-8");
             serializer.startDocument("UTF-8", false);
@@ -331,11 +342,14 @@
         //serializer.endTag(ns, RESULT_TAG);
     }
 
+    private File getResultFile(File reportDir) {
+        return new File(reportDir, TEST_RESULT_FILE_NAME);
+    }
+
     /**
      * Creates the output stream to use for test results. Exposed for mocking.
      */
-    OutputStream createOutputResultStream(File reportDir) throws IOException {
-        File reportFile = new File(reportDir, TEST_RESULT_FILE_NAME);
+    OutputStream createOutputResultStream(File reportFile) throws IOException {
         logResult("Created xml report file at file://%s", reportFile.getAbsolutePath());
         return new FileOutputStream(reportFile);
     }
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/IssueReporter.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/IssueReporter.java
index 8ee9c0f..9d903dd 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/IssueReporter.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/IssueReporter.java
@@ -28,11 +28,6 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.net.HttpURLConnection;
-import java.net.URL;
 import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
@@ -46,7 +41,6 @@
  */
 public class IssueReporter implements ITestInvocationListener {
 
-    private static final String FORM_DATA_BOUNDARY = "C75I55u3R3p0r73r";
     private static final int BUGREPORT_SIZE = 500 * 1024;
 
     private static final String PRODUCT_NAME_KEY = "buildName";
@@ -87,22 +81,42 @@
      */
     private void setBugReport(InputStreamSource dataStream) throws IOException {
         if (mCurrentIssue != null) {
-            InputStream input = dataStream.createInputStream();
-            ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(BUGREPORT_SIZE);
-            GZIPOutputStream gzipOutput = new GZIPOutputStream(byteOutput);
-            for (byte[] buffer = new byte[1024]; input.read(buffer) >= 0; ) {
-                gzipOutput.write(buffer);
-            }
-            gzipOutput.close();
-
             // Only one bug report can be stored at a time and they are gzipped to
             // about 0.5 MB so there shoudn't be any memory leak bringing down CTS.
-            mCurrentIssue.mBugReport = byteOutput.toByteArray();
+            InputStream input = null;
+            try {
+                input = dataStream.createInputStream();
+                mCurrentIssue.mBugReport = getBytes(input, BUGREPORT_SIZE);
+            } finally {
+                if (input != null) {
+                    input.close();
+                }
+            }
         } else {
             CLog.e("setBugReport is getting called on an empty issue...");
         }
     }
 
+    /**
+     * @param input that will be gzipped and returne as a byte array
+     * @param size of the output expected
+     * @return the byte array with the input's data
+     * @throws IOException
+     */
+    static byte[] getBytes(InputStream input, int size) throws IOException {
+        ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(size);
+        GZIPOutputStream gzipOutput = new GZIPOutputStream(byteOutput);
+        for (byte[] buffer = new byte[1024]; ; ) {
+            int numRead = input.read(buffer);
+            if (numRead < 0) {
+                break;
+            }
+            gzipOutput.write(buffer, 0, numRead);
+        }
+        gzipOutput.close();
+        return byteOutput.toByteArray();
+    }
+
     @Override
     public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
         if (mCurrentIssue != null) {
@@ -158,32 +172,14 @@
                 return null;
             }
 
-            HttpURLConnection connection = null;
-
-            try {
-                URL url = new URL(mServerUrl);
-                connection = (HttpURLConnection) url.openConnection();
-                connection.setRequestMethod("POST");
-                connection.setDoOutput(true);
-                connection.setRequestProperty("Content-Type",
-                        "multipart/form-data; boundary=" + FORM_DATA_BOUNDARY);
-
-                byte[] body = getContentBody();
-                connection.setRequestProperty("Content-Length", Integer.toString(body.length));
-
-                OutputStream output = connection.getOutputStream();
-                output.write(body);
-                output.close();
-
-                // Open the stream to get a response. Otherwise request will be cancelled.
-                InputStream input = connection.getInputStream();
-                input.close();
-
-            } finally {
-                if (connection != null) {
-                    connection.disconnect();
-                }
-            }
+            new MultipartForm(mServerUrl)
+                    .addFormValue("productName", mProductName)
+                    .addFormValue("buildType", mBuildType)
+                    .addFormValue("buildId", mBuildId)
+                    .addFormValue("testName", mTestName)
+                    .addFormValue("stackTrace", mStackTrace)
+                    .addFormFile("bugReport", "bugreport.txt.gz", mBugReport)
+                    .submit();
 
             return null;
         }
@@ -191,43 +187,6 @@
         private boolean isEmpty(String value) {
             return value == null || value.trim().isEmpty();
         }
-
-        private byte[] getContentBody() throws IOException {
-            ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
-            PrintWriter writer = new PrintWriter(new OutputStreamWriter(byteOutput));
-            writer.println();
-            writeFormField(writer, "productName", mProductName);
-            writeFormField(writer, "buildType", mBuildType);
-            writeFormField(writer, "buildId", mBuildId);
-            writeFormField(writer, "testName", mTestName);
-            writeFormField(writer, "stackTrace", mStackTrace);
-            if (mBugReport != null) {
-                writeFormFileHeader(writer, "bugReport", "bugReport.txt.gz");
-                writer.flush(); // Must flush here before writing to the byte stream!
-                byteOutput.write(mBugReport);
-                writer.println();
-            }
-            writer.append("--").append(FORM_DATA_BOUNDARY).println("--");
-            writer.flush();
-            writer.close();
-            return byteOutput.toByteArray();
-        }
-
-        private void writeFormField(PrintWriter writer, String name, String value) {
-            writer.append("--").println(FORM_DATA_BOUNDARY);
-            writer.append("Content-Disposition: form-data; name=\"").append(name).println("\"");
-            writer.println();
-            writer.println(value);
-        }
-
-        private void writeFormFileHeader(PrintWriter writer, String name, String fileName) {
-            writer.append("--").println(FORM_DATA_BOUNDARY);
-            writer.append("Content-Disposition: form-data; name=\"").append(name);
-            writer.append("\"; filename=\"").append(fileName).println("\"");
-            writer.println("Content-Type: application/x-gzip");
-            writer.println("Content-Transfer-Encoding: binary");
-            writer.println();
-        }
     }
 
     @Override
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/MultipartForm.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/MultipartForm.java
new file mode 100644
index 0000000..f3ef0bb
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/MultipartForm.java
@@ -0,0 +1,144 @@
+/*
+ * 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.cts.tradefed.result;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+/** MultipartForm builds a multipart form and submits it. */
+class MultipartForm {
+
+    private static final String FORM_DATA_BOUNDARY = "C75I55u3R3p0r73r";
+
+    private final String mServerUrl;
+
+    private final Map<String, String> mFormValues = new HashMap<String, String>();
+
+    private String mName;
+    private String mFileName;
+    private byte[] mData;
+
+    public MultipartForm(String serverUrl) {
+        mServerUrl = serverUrl;
+    }
+
+    public MultipartForm addFormValue(String name, String value) {
+        mFormValues.put(name, value);
+        return this;
+    }
+
+    public MultipartForm addFormFile(String name, String fileName, byte[] data) {
+        mName = name;
+        mFileName = fileName;
+        mData = data;
+        return this;
+    }
+
+    public void submit() throws IOException {
+        String redirectUrl = submitForm(mServerUrl);
+        if (redirectUrl != null) {
+            submitForm(redirectUrl);
+        }
+    }
+
+    /**
+     * @param serverUrl to post the data to
+     * @return a url if the server redirected to another url
+     * @throws IOException
+     */
+    private String submitForm(String serverUrl) throws IOException {
+        HttpURLConnection connection = null;
+        try {
+            URL url = new URL(serverUrl);
+            connection = (HttpURLConnection) url.openConnection();
+            connection.setInstanceFollowRedirects(false);
+            connection.setRequestMethod("POST");
+            connection.setDoOutput(true);
+            connection.setRequestProperty("Content-Type",
+                    "multipart/form-data; boundary=" + FORM_DATA_BOUNDARY);
+
+            byte[] body = getContentBody();
+            connection.setRequestProperty("Content-Length", Integer.toString(body.length));
+
+            OutputStream output = connection.getOutputStream();
+            try {
+                output.write(body);
+            } finally {
+                output.close();
+            }
+
+            // Open the stream to get a response. Otherwise request will be cancelled.
+            InputStream input = connection.getInputStream();
+            input.close();
+
+            if (connection.getResponseCode() == 302) {
+                return connection.getHeaderField("Location");
+            }
+        } finally {
+            if (connection != null) {
+                connection.disconnect();
+            }
+        }
+
+        return null;
+    }
+
+    private byte[] getContentBody() throws IOException {
+        ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
+        PrintWriter writer = new PrintWriter(new OutputStreamWriter(byteOutput));
+        writer.println();
+
+        for (Map.Entry<String, String> formValue : mFormValues.entrySet()) {
+            writeFormField(writer, formValue.getKey(), formValue.getValue());
+        }
+
+        if (mData != null) {
+            writeFormFileHeader(writer, mName, mFileName);
+            writer.flush(); // Must flush here before writing to the byte stream!
+            byteOutput.write(mData);
+            writer.println();
+        }
+        writer.append("--").append(FORM_DATA_BOUNDARY).println("--");
+        writer.flush();
+        writer.close();
+        return byteOutput.toByteArray();
+    }
+
+    private void writeFormField(PrintWriter writer, String name, String value) {
+        writer.append("--").println(FORM_DATA_BOUNDARY);
+        writer.append("Content-Disposition: form-data; name=\"").append(name).println("\"");
+        writer.println();
+        writer.println(value);
+    }
+
+    private void writeFormFileHeader(PrintWriter writer, String name, String fileName) {
+        writer.append("--").println(FORM_DATA_BOUNDARY);
+        writer.append("Content-Disposition: form-data; name=\"").append(name);
+        writer.append("\"; filename=\"").append(fileName).println("\"");
+        writer.println("Content-Type: application/x-gzip");
+        writer.println("Content-Transfer-Encoding: binary");
+        writer.println();
+    }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/ResultReporter.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/ResultReporter.java
new file mode 100644
index 0000000..05192c9
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/ResultReporter.java
@@ -0,0 +1,60 @@
+/*
+ * 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.cts.tradefed.result;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Class that sends a HTTP POST multipart/form-data request containing
+ * the test result XML.
+ */
+class ResultReporter {
+
+    private static final int RESULT_XML_BYTES = 500 * 1024;
+
+    private final String mServerUrl;
+
+    private final File mReportFile;
+
+    ResultReporter(String serverUrl, File reportFile) {
+        mServerUrl = serverUrl;
+        mReportFile = reportFile;
+    }
+
+    public void reportResult() throws IOException {
+        if (isEmpty(mServerUrl)) {
+            return;
+        }
+
+        InputStream input = new FileInputStream(mReportFile);
+        try {
+            byte[] data = IssueReporter.getBytes(input, RESULT_XML_BYTES);
+            new MultipartForm(mServerUrl)
+                    .addFormFile("resultXml", "testResult.xml.gz", data)
+                    .submit();
+        } finally {
+            input.close();
+        }
+    }
+
+    private boolean isEmpty(String value) {
+        return value == null || value.trim().isEmpty();
+    }
+}