Merge "surfaceflinger: give hwcomposer a chance to release buffers"
diff --git a/api/current.xml b/api/current.xml
index 017fcba..589ec10 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -2176,6 +2176,17 @@
  visibility="public"
 >
 </field>
+<field name="actionOverflowButtonStyle"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843574"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="activityCloseEnterAnimation"
  type="int"
  transient="false"
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index fb88c71..b1fdec8 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -41,6 +41,7 @@
     private static final int MAX_AGE_MILLISECONDS = 200;
     
     private static final int POINTER_POOL_CAPACITY = 20;
+    private static final int INVALID_POINTER = -1;
 
     private static final Pool<VelocityTracker> sPool = Pools.synchronizedPool(
             Pools.finitePool(new PoolableManager<VelocityTracker>() {
@@ -76,6 +77,7 @@
     private Pointer mPointerListHead; // sorted by id in increasing order
     private int mLastTouchIndex;
     private int mGeneration;
+    private int mActivePointerId;
 
     private VelocityTracker mNext;
 
@@ -125,6 +127,7 @@
         
         mPointerListHead = null;
         mLastTouchIndex = 0;
+        mActivePointerId = INVALID_POINTER;
     }
     
     /**
@@ -180,6 +183,10 @@
                 // Pointer went down.  Add it to the list.
                 // Write a sentinel at the end of the pastTime trace so we will be able to
                 // tell when the trace started.
+                if (mActivePointerId == INVALID_POINTER) {
+                    // Congratulations! You're the new active pointer!
+                    mActivePointerId = pointerId;
+                }
                 pointer = obtainPointer();
                 pointer.id = pointerId;
                 pointer.pastTime[lastTouchIndex] = Long.MIN_VALUE;
@@ -214,6 +221,7 @@
         previousPointer = null;
         for (Pointer pointer = mPointerListHead; pointer != null; ) {
             final Pointer nextPointer = pointer.next;
+            final int pointerId = pointer.id;
             if (pointer.generation != generation) {
                 // Pointer went up.  Remove it from the list.
                 if (previousPointer == null) {
@@ -222,6 +230,12 @@
                     previousPointer.next = nextPointer;
                 }
                 releasePointer(pointer);
+
+                if (pointerId == mActivePointerId) {
+                    // Pick a new active pointer. How is arbitrary.
+                    mActivePointerId = mPointerListHead != null ?
+                            mPointerListHead.id : INVALID_POINTER;
+                }
             } else {
                 previousPointer = pointer;
             }
@@ -334,7 +348,7 @@
      * @return The previously computed X velocity.
      */
     public float getXVelocity() {
-        Pointer pointer = getPointer(0);
+        Pointer pointer = getPointer(mActivePointerId);
         return pointer != null ? pointer.xVelocity : 0;
     }
     
@@ -345,7 +359,7 @@
      * @return The previously computed Y velocity.
      */
     public float getYVelocity() {
-        Pointer pointer = getPointer(0);
+        Pointer pointer = getPointer(mActivePointerId);
         return pointer != null ? pointer.yVelocity : 0;
     }
     
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index dabe7ba..c87264a 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4448,7 +4448,7 @@
         }
 
         hideControllers();
-        
+
         switch (keyCode) {
             case KeyEvent.KEYCODE_DPAD_CENTER:
                 /*
@@ -5875,7 +5875,10 @@
      * Return true iff there is a selection inside this text view.
      */
     public boolean hasSelection() {
-        return getSelectionStart() != getSelectionEnd();
+        final int selectionStart = getSelectionStart();
+        final int selectionEnd = getSelectionEnd();
+
+        return selectionStart >= 0 && selectionStart != selectionEnd;
     }
 
     /**
@@ -6539,7 +6542,7 @@
         mShowCursor = SystemClock.uptimeMillis();
 
         ensureEndedBatchEdit();
-        
+
         if (focused) {
             int selStart = getSelectionStart();
             int selEnd = getSelectionEnd();
@@ -7068,7 +7071,7 @@
             return false;
         }
 
-        if (mText.length() > 0 && getSelectionStart() >= 0) {
+        if (mText.length() > 0 && hasSelection()) {
             if (mText instanceof Editable && mInput != null) {
                 return true;
             }
@@ -7082,7 +7085,7 @@
             return false;
         }
 
-        if (mText.length() > 0 && getSelectionStart() >= 0) {
+        if (mText.length() > 0 && hasSelection()) {
             return true;
         }
 
@@ -7203,6 +7206,49 @@
         int minOffset = selectionModifierCursorController.getMinTouchOffset();
         int maxOffset = selectionModifierCursorController.getMaxTouchOffset();
 
+        if (minOffset == maxOffset) {
+            int offset = Math.max(0, Math.min(minOffset, mTransformed.length()));
+
+            // Tolerance, number of charaters around tapped position
+            final int range = 1;
+            final int max = mTransformed.length() - 1;
+
+            // 'Smart' word selection: detect position between words
+            for (int i = -range; i <= range; i++) {
+                int index = offset + i;
+                if (index >= 0 && index <= max) {
+                    if (Character.isSpaceChar(mTransformed.charAt(index))) {
+                        // Select current space
+                        selectionStart = index;
+                        selectionEnd = selectionStart + 1;
+
+                        // Extend selection to maximum space range
+                        while (selectionStart > 0 &&
+                                Character.isSpaceChar(mTransformed.charAt(selectionStart - 1))) {
+                            selectionStart--;
+                        }
+                        while (selectionEnd < max &&
+                                Character.isSpaceChar(mTransformed.charAt(selectionEnd))) {
+                            selectionEnd++;
+                        }
+
+                        Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
+                        return;
+                    }
+                }
+            }
+
+            // 'Smart' word selection: detect position at beginning or end of text.
+            if (offset <= range) {
+                Selection.setSelection((Spannable) mText, 0, 0);
+                return;
+            }
+            if (offset >= (max - range)) {
+                Selection.setSelection((Spannable) mText, max + 1, max + 1);
+                return;
+            }
+        }
+
         long wordLimits = getWordLimitsAt(minOffset);
         if (wordLimits >= 0) {
             selectionStart = (int) (wordLimits >>> 32);
diff --git a/core/java/com/android/internal/view/menu/ActionMenuView.java b/core/java/com/android/internal/view/menu/ActionMenuView.java
index e064e2c..c4b6214 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuView.java
@@ -213,13 +213,11 @@
 
     private class OverflowMenuButton extends ImageButton {
         public OverflowMenuButton(Context context) {
-            super(context, null, com.android.internal.R.attr.actionButtonStyle);
+            super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle);
 
             final Resources res = context.getResources();
             setClickable(true);
             setFocusable(true);
-            setContentDescription(res.getString(com.android.internal.R.string.more_item_label));
-            setImageDrawable(res.getDrawable(com.android.internal.R.drawable.ic_menu_more));
             setVisibility(VISIBLE);
             setEnabled(true);
         }
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 3130b20..f466e82 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -477,6 +477,7 @@
         <attr name="actionBarTabStyle" format="reference" />
         <attr name="actionBarTabBarStyle" format="reference" />
         <attr name="actionBarTabTextStyle" format="reference" />
+        <attr name="actionOverflowButtonStyle" format="reference" />
 
         <!-- =================== -->
         <!-- Action mode styles  -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 988176d..dbfba2e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -80,6 +80,10 @@
          for backward compatibility with apps that require external storage. -->
     <bool name="config_emulateExternalStorage">false</bool>
 
+    <!-- Set to true if external storage is case sensitive.
+         Typically external storage is FAT, which is case insensitive. -->
+    <bool name="config_caseSensitiveExternalStorage">false</bool>
+
     <!-- XXXXX NOTE THE FOLLOWING RESOURCES USE THE WRONG NAMING CONVENTION.
          Please don't copy them, copy anything else. -->
 
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 515f817..7f168a1 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1338,6 +1338,7 @@
   <public type="attr" name="actionBarTabStyle" />
   <public type="attr" name="actionBarTabBarStyle" />
   <public type="attr" name="actionBarTabTextStyle" />
+  <public type="attr" name="actionOverflowButtonStyle" />
 
   <public type="anim" name="animator_fade_in" />
   <public type="anim" name="animator_fade_out" />
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index bbdad62..5c3870d 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -894,6 +894,11 @@
         <item name="android:background">@null</item>
     </style>
 
+    <style name="Widget.ActionButton.Overflow">
+        <item name="android:src">@drawable/ic_menu_more</item>
+        <item name="android:contentDescription">@string/more_item_label</item>
+    </style>
+
     <style name="Widget.ActionBarView_TabView">
         <item name="android:background">@drawable/minitab_lt</item>
         <item name="android:paddingLeft">4dip</item>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 652121c..7b3a47c 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -183,6 +183,7 @@
         <item name="dropDownSpinnerStyle">@android:style/Widget.Spinner.DropDown</item>
         <item name="actionDropDownStyle">@android:style/Widget.Spinner.DropDown</item>
         <item name="actionButtonStyle">@android:style/Widget.ActionButton</item>
+        <item name="actionOverflowButtonStyle">@android:style/Widget.ActionButton.Overflow</item>
         <item name="starStyle">@android:style/Widget.CompoundButton.Star</item>
         <item name="tabWidgetStyle">@android:style/Widget.TabWidget</item>
         <item name="textViewStyle">@android:style/Widget.TextView</item>
diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk
index 693ef18..b496805 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -12,7 +12,7 @@
 	$(call all-java-files-under, EnabledTestApp/src)
 
 LOCAL_DX_FLAGS := --core-library
-LOCAL_STATIC_JAVA_LIBRARIES := core-tests-supportlib android-common
+LOCAL_STATIC_JAVA_LIBRARIES := core-tests-supportlib android-common frameworks-core-util-lib
 LOCAL_JAVA_LIBRARIES := android.test.runner
 LOCAL_PACKAGE_NAME := FrameworksCoreTests
 
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index ce73ae1..f09421b 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -36,12 +36,16 @@
             android:description="@string/permdesc_testDenied" />
 
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
     <uses-permission android:name="android.permission.BROADCAST_STICKY" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
     <uses-permission android:name="android.permission.CLEAR_APP_CACHE" />
     <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
     <uses-permission android:name="android.permission.DELETE_CACHE_FILES" />
+    <uses-permission android:name="android.permission.DOWNLOAD_CACHE_NON_PURGEABLE" />
     <uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.READ_CONTACTS" />
diff --git a/core/tests/coretests/src/android/net/DownloadManagerBaseTest.java b/core/tests/coretests/src/android/net/DownloadManagerBaseTest.java
new file mode 100644
index 0000000..ee0f5f1
--- /dev/null
+++ b/core/tests/coretests/src/android/net/DownloadManagerBaseTest.java
@@ -0,0 +1,888 @@
+/*
+ * 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 android.net;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.Cursor;
+import android.net.ConnectivityManager;
+import android.net.DownloadManager;
+import android.net.NetworkInfo;
+import android.net.DownloadManager.Query;
+import android.net.DownloadManager.Request;
+import android.net.wifi.WifiManager;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.os.ParcelFileDescriptor.AutoCloseInputStream;
+import android.provider.Settings;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.concurrent.TimeoutException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import java.util.Vector;
+
+import junit.framework.AssertionFailedError;
+
+import coretestutils.http.MockResponse;
+import coretestutils.http.MockWebServer;
+
+/**
+ * Base class for Instrumented tests for the Download Manager.
+ */
+public class DownloadManagerBaseTest extends InstrumentationTestCase {
+
+    protected DownloadManager mDownloadManager = null;
+    protected MockWebServer mServer = null;
+    protected String mFileType = "text/plain";
+    protected Context mContext = null;
+    protected static final int DEFAULT_FILE_SIZE = 130 * 1024;  // 130kb
+    protected static final int FILE_BLOCK_READ_SIZE = 1024 * 1024;
+
+    protected static final String LOG_TAG = "android.net.DownloadManagerBaseTest";
+    protected static final int HTTP_OK = 200;
+    protected static final int HTTP_PARTIAL_CONTENT = 206;
+    protected static final int HTTP_NOT_FOUND = 404;
+    protected static final int HTTP_SERVICE_UNAVAILABLE = 503;
+    protected String DEFAULT_FILENAME = "somefile.txt";
+
+    protected static final int DEFAULT_MAX_WAIT_TIME = 2 * 60 * 1000;  // 2 minutes
+    protected static final int DEFAULT_WAIT_POLL_TIME = 5 * 1000;  // 5 seconds
+
+    protected static final int WAIT_FOR_DOWNLOAD_POLL_TIME = 1 * 1000;  // 1 second
+    protected static final int MAX_WAIT_FOR_DOWNLOAD_TIME = 5 * 60 * 1000; // 5 minutes
+
+    // Just a few popular file types used to return from a download
+    protected enum DownloadFileType {
+        PLAINTEXT,
+        APK,
+        GIF,
+        GARBAGE,
+        UNRECOGNIZED,
+        ZIP
+    }
+
+    protected enum DataType {
+        TEXT,
+        BINARY
+    }
+
+    public static class LoggingRng extends Random {
+
+        /**
+         * Constructor
+         *
+         * Creates RNG with self-generated seed value.
+         */
+        public LoggingRng() {
+            this(SystemClock.uptimeMillis());
+        }
+
+        /**
+         * Constructor
+         *
+         * Creats RNG with given initial seed value
+
+         * @param seed The initial seed value
+         */
+        public LoggingRng(long seed) {
+            super(seed);
+            Log.i(LOG_TAG, "Seeding RNG with value: " + seed);
+        }
+    }
+
+    public static class MultipleDownloadsCompletedReceiver extends BroadcastReceiver {
+        private volatile int mNumDownloadsCompleted = 0;
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equalsIgnoreCase(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
+                ++mNumDownloadsCompleted;
+                Log.i(LOG_TAG, "MultipleDownloadsCompletedReceiver got intent: " +
+                        intent.getAction() + " --> total count: " + mNumDownloadsCompleted);
+            }
+        }
+
+        /**
+         * Gets the number of times the {@link #onReceive} callback has been called for the
+         * {@link DownloadManager.ACTION_DOWNLOAD_COMPLETED} action, indicating the number of
+         * downloads completed thus far.
+         *
+         * @return the number of downloads completed so far.
+         */
+        public int numDownloadsCompleted() {
+            return mNumDownloadsCompleted;
+        }
+    }
+
+    public static class WiFiChangedReceiver extends BroadcastReceiver {
+        private Context mContext = null;
+
+        /**
+         * Constructor
+         *
+         * Sets the current state of WiFi.
+         *
+         * @param context The current app {@link Context}.
+         */
+        public WiFiChangedReceiver(Context context) {
+            mContext = context;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equalsIgnoreCase(ConnectivityManager.CONNECTIVITY_ACTION)) {
+                Log.i(LOG_TAG, "ConnectivityManager state change: " + intent.getAction());
+                synchronized (this) {
+                    this.notify();
+                }
+            }
+        }
+
+        /**
+         * Gets the current state of WiFi.
+         *
+         * @return Returns true if WiFi is on, false otherwise.
+         */
+        public boolean getWiFiIsOn() {
+            ConnectivityManager connManager = (ConnectivityManager)mContext.getSystemService(
+                    Context.CONNECTIVITY_SERVICE);
+            NetworkInfo info = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+            return info.isConnected();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setUp() throws Exception {
+        mContext = getInstrumentation().getContext();
+        mDownloadManager = (DownloadManager)mContext.getSystemService(Context.DOWNLOAD_SERVICE);
+        mServer = new MockWebServer();
+        // Note: callers overriding this should call mServer.play() with the desired port #
+    }
+
+    /**
+     * Helper to enqueue a response from the MockWebServer.
+     *
+     * @param status The HTTP status code to return for this response
+     * @param body The body to return in this response
+     * @return Returns the mock web server response that was queued (which can be modified)
+     */
+    protected MockResponse enqueueResponse(int status, byte[] body) {
+        return doEnqueueResponse(status).setBody(body);
+
+    }
+
+    /**
+     * Helper to enqueue a response from the MockWebServer.
+     *
+     * @param status The HTTP status code to return for this response
+     * @param bodyFile The body to return in this response
+     * @return Returns the mock web server response that was queued (which can be modified)
+     */
+    protected MockResponse enqueueResponse(int status, File bodyFile) {
+        return doEnqueueResponse(status).setBody(bodyFile);
+    }
+
+    /**
+     * Helper for enqueue'ing a response from the MockWebServer.
+     *
+     * @param status The HTTP status code to return for this response
+     * @return Returns the mock web server response that was queued (which can be modified)
+     */
+    protected MockResponse doEnqueueResponse(int status) {
+        MockResponse response = new MockResponse().setResponseCode(status);
+        response.addHeader("Content-type", mFileType);
+        mServer.enqueue(response);
+        return response;
+    }
+
+    /**
+     * Helper to generate a random blob of bytes.
+     *
+     * @param size The size of the data to generate
+     * @param type The type of data to generate: currently, one of {@link DataType.TEXT} or
+     *         {@link DataType.BINARY}.
+     * @return The random data that is generated.
+     */
+    protected byte[] generateData(int size, DataType type) {
+        return generateData(size, type, null);
+    }
+
+    /**
+     * Helper to generate a random blob of bytes using a given RNG.
+     *
+     * @param size The size of the data to generate
+     * @param type The type of data to generate: currently, one of {@link DataType.TEXT} or
+     *         {@link DataType.BINARY}.
+     * @param rng (optional) The RNG to use; pass null to use
+     * @return The random data that is generated.
+     */
+    protected byte[] generateData(int size, DataType type, Random rng) {
+        int min = Byte.MIN_VALUE;
+        int max = Byte.MAX_VALUE;
+
+        // Only use chars in the HTTP ASCII printable character range for Text
+        if (type == DataType.TEXT) {
+            min = 32;
+            max = 126;
+        }
+        byte[] result = new byte[size];
+        Log.i(LOG_TAG, "Generating data of size: " + size);
+
+        if (rng == null) {
+            rng = new LoggingRng();
+        }
+
+        for (int i = 0; i < size; ++i) {
+            result[i] = (byte) (min + rng.nextInt(max - min + 1));
+        }
+        return result;
+    }
+
+    /**
+     * Helper to verify the size of a file.
+     *
+     * @param pfd The input file to compare the size of
+     * @param size The expected size of the file
+     */
+    protected void verifyFileSize(ParcelFileDescriptor pfd, long size) {
+        assertEquals(pfd.getStatSize(), size);
+    }
+
+    /**
+     * Helper to verify the contents of a downloaded file versus a byte[].
+     *
+     * @param actual The file of whose contents to verify
+     * @param expected The data we expect to find in the aforementioned file
+     * @throws IOException if there was a problem reading from the file
+     */
+    protected void verifyFileContents(ParcelFileDescriptor actual, byte[] expected)
+            throws IOException {
+        AutoCloseInputStream input = new ParcelFileDescriptor.AutoCloseInputStream(actual);
+        long fileSize = actual.getStatSize();
+
+        assertTrue(fileSize <= Integer.MAX_VALUE);
+        assertEquals(expected.length, fileSize);
+
+        byte[] actualData = new byte[expected.length];
+        assertEquals(input.read(actualData), fileSize);
+        compareByteArrays(actualData, expected);
+    }
+
+    /**
+     * Helper to compare 2 byte arrays.
+     *
+     * @param actual The array whose data we want to verify
+     * @param expected The array of data we expect to see
+     */
+    protected void compareByteArrays(byte[] actual, byte[] expected) {
+        assertEquals(actual.length, expected.length);
+        int length = actual.length;
+        for (int i = 0; i < length; ++i) {
+            // assert has a bit of overhead, so only do the assert when the values are not the same
+            if (actual[i] != expected[i]) {
+                fail("Byte arrays are not equal.");
+            }
+        }
+    }
+
+    /**
+     * Verifies the contents of a downloaded file versus the contents of a File.
+     *
+     * @param pfd The file whose data we want to verify
+     * @param file The file containing the data we expect to see in the aforementioned file
+     * @throws IOException If there was a problem reading either of the two files
+     */
+    protected void verifyFileContents(ParcelFileDescriptor pfd, File file) throws IOException {
+        byte[] actual = new byte[FILE_BLOCK_READ_SIZE];
+        byte[] expected = new byte[FILE_BLOCK_READ_SIZE];
+
+        AutoCloseInputStream input = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+
+        assertEquals(file.length(), pfd.getStatSize());
+
+        DataInputStream inFile = new DataInputStream(new FileInputStream(file));
+        int actualRead = 0;
+        int expectedRead = 0;
+
+        while (((actualRead = input.read(actual)) != -1) &&
+                ((expectedRead = inFile.read(expected)) != -1)) {
+            assertEquals(actualRead, expectedRead);
+            compareByteArrays(actual, expected);
+        }
+    }
+
+    /**
+     * Sets the MIME type of file that will be served from the mock server
+     *
+     * @param type The MIME type to return from the server
+     */
+    protected void setServerMimeType(DownloadFileType type) {
+        mFileType = getMimeMapping(type);
+    }
+
+    /**
+     * Gets the MIME content string for a given type
+     *
+     * @param type The MIME type to return
+     * @return the String representation of that MIME content type
+     */
+    protected String getMimeMapping(DownloadFileType type) {
+        switch (type) {
+            case APK:
+                return "application/vnd.android.package-archive";
+            case GIF:
+                return "image/gif";
+            case ZIP:
+                return "application/x-zip-compressed";
+            case GARBAGE:
+                return "zip\\pidy/doo/da";
+            case UNRECOGNIZED:
+                return "application/new.undefined.type.of.app";
+        }
+        return "text/plain";
+    }
+
+    /**
+     * Gets the Uri that should be used to access the mock server
+     *
+     * @param filename The name of the file to try to retrieve from the mock server
+     * @return the Uri to use for access the file on the mock server
+     */
+    protected Uri getServerUri(String filename) throws Exception {
+        URL url = mServer.getUrl("/" + filename);
+        return Uri.parse(url.toString());
+    }
+
+   /**
+    * Gets the Uri that should be used to access the mock server
+    *
+    * @param filename The name of the file to try to retrieve from the mock server
+    * @return the Uri to use for access the file on the mock server
+    */
+    protected void logDBColumnData(Cursor cursor, String column) {
+        int index = cursor.getColumnIndex(column);
+        Log.i(LOG_TAG, "columnName: " + column);
+        Log.i(LOG_TAG, "columnValue: " + cursor.getString(index));
+    }
+
+    /**
+     * Helper to create and register a new MultipleDownloadCompletedReciever
+     *
+     * This is used to track many simultaneous downloads by keeping count of all the downloads
+     * that have completed.
+     *
+     * @return A new receiver that records and can be queried on how many downloads have completed.
+     */
+    protected MultipleDownloadsCompletedReceiver registerNewMultipleDownloadsReceiver() {
+        MultipleDownloadsCompletedReceiver receiver = new MultipleDownloadsCompletedReceiver();
+        mContext.registerReceiver(receiver, new IntentFilter(
+                DownloadManager.ACTION_DOWNLOAD_COMPLETE));
+        return receiver;
+    }
+
+    /**
+     * Helper to verify a standard single-file download from the mock server, and clean up after
+     * verification
+     *
+     * Note that this also calls the Download manager's remove, which cleans up the file from cache.
+     *
+     * @param requestId The id of the download to remove
+     * @param fileData The data to verify the file contains
+     */
+    protected void verifyAndCleanupSingleFileDownload(long requestId, byte[] fileData)
+            throws Exception {
+        int fileSize = fileData.length;
+        ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(requestId);
+        Cursor cursor = mDownloadManager.query(new Query().setFilterById(requestId));
+
+        try {
+            assertEquals(1, cursor.getCount());
+            assertTrue(cursor.moveToFirst());
+
+            mServer.checkForExceptions();
+
+            verifyFileSize(pfd, fileSize);
+            verifyFileContents(pfd, fileData);
+        } finally {
+            pfd.close();
+            cursor.close();
+            mDownloadManager.remove(requestId);
+        }
+    }
+
+    /**
+     * Enables or disables WiFi.
+     *
+     * Note: Needs the following permissions:
+     *  android.permission.ACCESS_WIFI_STATE
+     *  android.permission.CHANGE_WIFI_STATE
+     * @param enable true if it should be enabled, false if it should be disabled
+     */
+    protected void setWiFiStateOn(boolean enable) throws Exception {
+        WifiManager manager = (WifiManager)mContext.getSystemService(Context.WIFI_SERVICE);
+
+        manager.setWifiEnabled(enable);
+
+        String timeoutMessage = "Timed out waiting for Wifi to be "
+            + (enable ? "enabled!" : "disabled!");
+
+        WiFiChangedReceiver receiver = new WiFiChangedReceiver(mContext);
+        mContext.registerReceiver(receiver, new IntentFilter(
+                ConnectivityManager.CONNECTIVITY_ACTION));
+
+        synchronized (receiver) {
+            long timeoutTime = SystemClock.elapsedRealtime() + DEFAULT_MAX_WAIT_TIME;
+            boolean timedOut = false;
+
+            while (receiver.getWiFiIsOn() != enable && !timedOut) {
+                try {
+                    receiver.wait(DEFAULT_MAX_WAIT_TIME);
+
+                    if (SystemClock.elapsedRealtime() > timeoutTime) {
+                        timedOut = true;
+                    }
+                }
+                catch (InterruptedException e) {
+                    // ignore InterruptedExceptions
+                }
+            }
+            if (timedOut) {
+                fail(timeoutMessage);
+            }
+        }
+        assertEquals(enable, receiver.getWiFiIsOn());
+    }
+
+    /**
+     * Helper to enables or disables airplane mode. If successful, it also broadcasts an intent
+     * indicating that the mode has changed.
+     *
+     * Note: Needs the following permission:
+     *  android.permission.WRITE_SETTINGS
+     * @param enable true if airplane mode should be ON, false if it should be OFF
+     */
+    protected void setAirplaneModeOn(boolean enable) throws Exception {
+        int state = enable ? 1 : 0;
+
+        // Change the system setting
+        Settings.System.putInt(mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON,
+                state);
+
+        String timeoutMessage = "Timed out waiting for airplane mode to be " +
+                (enable ? "enabled!" : "disabled!");
+
+        // wait for airplane mode to change state
+        int currentWaitTime = 0;
+        while (Settings.System.getInt(mContext.getContentResolver(),
+                Settings.System.AIRPLANE_MODE_ON, -1) != state) {
+            timeoutWait(currentWaitTime, DEFAULT_WAIT_POLL_TIME, DEFAULT_MAX_WAIT_TIME,
+                    timeoutMessage);
+        }
+
+        // Post the intent
+        Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        intent.putExtra("state", true);
+        mContext.sendBroadcast(intent);
+    }
+
+    /**
+     * Helper to create a large file of random data on the SD card.
+     *
+     * @param filename (optional) The name of the file to create on the SD card; pass in null to
+     *          use a default temp filename.
+     * @param type The type of file to create
+     * @param subdirectory If not null, the subdirectory under the SD card where the file should go
+     * @return The File that was created
+     * @throws IOException if there was an error while creating the file.
+     */
+    protected File createFileOnSD(String filename, long fileSize, DataType type,
+            String subdirectory) throws IOException {
+
+        // Build up the file path and name
+        String sdPath = Environment.getExternalStorageDirectory().getPath();
+        StringBuilder fullPath = new StringBuilder(sdPath);
+        if (subdirectory != null) {
+            fullPath.append(File.separatorChar).append(subdirectory);
+        }
+
+        File file = null;
+        if (filename == null) {
+            file = File.createTempFile("DMTEST_", null, new File(fullPath.toString()));
+        }
+        else {
+            fullPath.append(File.separatorChar).append(filename);
+            file = new File(fullPath.toString());
+            file.createNewFile();
+        }
+
+        // Fill the file with random data
+        DataOutputStream output = new DataOutputStream(new FileOutputStream(file));
+        final int CHUNK_SIZE = 1000000;  // copy random data in 1000000-char chunks
+        long remaining = fileSize;
+        int nextChunkSize = CHUNK_SIZE;
+        byte[] randomData = null;
+        Random rng = new LoggingRng();
+
+        try {
+            while (remaining > 0) {
+                if (remaining < CHUNK_SIZE) {
+                    nextChunkSize = (int)remaining;
+                    remaining = 0;
+                }
+                else {
+                    remaining -= CHUNK_SIZE;
+                }
+
+                randomData = generateData(nextChunkSize, type, rng);
+                output.write(randomData);
+            }
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "Error writing to file " + file.getAbsolutePath());
+            file.delete();
+            throw e;
+        } finally {
+            output.close();
+        }
+        return file;
+    }
+
+    /**
+     * Helper to wait for a particular download to finish, or else a timeout to occur
+     *
+     * @param id The download id to query on (wait for)
+     */
+    protected void waitForDownloadOrTimeout(long id) throws TimeoutException,
+            InterruptedException {
+        waitForDownloadOrTimeout(id, WAIT_FOR_DOWNLOAD_POLL_TIME, MAX_WAIT_FOR_DOWNLOAD_TIME);
+    }
+
+    /**
+     * Helper to wait for a particular download to finish, or else a timeout to occur
+     *
+     * @param id The download id to query on (wait for)
+     * @param poll The amount of time to wait
+     * @param timeoutMillis The max time (in ms) to wait for the download(s) to complete
+     */
+    protected void waitForDownloadOrTimeout(long id, long poll, long timeoutMillis)
+            throws TimeoutException, InterruptedException {
+        doWaitForDownloadsOrTimeout(new Query().setFilterById(id), poll, timeoutMillis);
+    }
+
+    /**
+     * Helper to wait for all downloads to finish, or else a specified timeout to occur
+     *
+     * @param poll The amount of time to wait
+     * @param timeoutMillis The max time (in ms) to wait for the download(s) to complete
+     */
+    protected void waitForDownloadsOrTimeout(long poll, long timeoutMillis) throws TimeoutException,
+            InterruptedException {
+        doWaitForDownloadsOrTimeout(new Query(), poll, timeoutMillis);
+    }
+
+    /**
+     * Helper to wait for all downloads to finish, or else a timeout to occur, but does not throw
+     *
+     * @param id The id of the download to query against
+     * @param poll The amount of time to wait
+     * @param timeoutMillis The max time (in ms) to wait for the download(s) to complete
+     * @return true if download completed successfully (didn't timeout), false otherwise
+     */
+    protected boolean waitForDownloadOrTimeoutNoThrow(long id, long poll, long timeoutMillis) {
+        try {
+            doWaitForDownloadsOrTimeout(new Query().setFilterById(id), poll, timeoutMillis);
+        } catch (TimeoutException e) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Helper function to synchronously wait, or timeout if the maximum threshold has been exceeded.
+     *
+     * @param currentTotalWaitTime The total time waited so far
+     * @param poll The amount of time to wait
+     * @param maxTimeoutMillis The total wait time threshold; if we've waited more than this long,
+     *          we timeout and fail
+     * @param timedOutMessage The message to display in the failure message if we timeout
+     * @return The new total amount of time we've waited so far
+     * @throws TimeoutException if timed out waiting for SD card to mount
+     */
+    protected int timeoutWait(int currentTotalWaitTime, long poll, long maxTimeoutMillis,
+            String timedOutMessage) throws TimeoutException {
+        long now = SystemClock.elapsedRealtime();
+        long end = now + poll;
+
+        // if we get InterruptedException's, ignore them and just keep sleeping
+        while (now < end) {
+            try {
+                Thread.sleep(end - now);
+            } catch (InterruptedException e) {
+                // ignore interrupted exceptions
+            }
+            now = SystemClock.elapsedRealtime();
+        }
+
+        currentTotalWaitTime += poll;
+        if (currentTotalWaitTime > maxTimeoutMillis) {
+            throw new TimeoutException(timedOutMessage);
+        }
+        return currentTotalWaitTime;
+    }
+
+    /**
+     * Helper to wait for all downloads to finish, or else a timeout to occur
+     *
+     * @param query The query to pass to the download manager
+     * @param poll The poll time to wait between checks
+     * @param timeoutMillis The max amount of time (in ms) to wait for the download(s) to complete
+     */
+    protected void doWaitForDownloadsOrTimeout(Query query, long poll, long timeoutMillis)
+            throws TimeoutException {
+        int currentWaitTime = 0;
+        while (true) {
+            query.setFilterByStatus(DownloadManager.STATUS_PENDING | DownloadManager.STATUS_PAUSED
+                    | DownloadManager.STATUS_RUNNING);
+            Cursor cursor = mDownloadManager.query(query);
+
+            try {
+                // If we've finished the downloads then we're done
+                if (cursor.getCount() == 0) {
+                    break;
+                }
+                currentWaitTime = timeoutWait(currentWaitTime, poll, timeoutMillis,
+                        "Timed out waiting for all downloads to finish");
+            } finally {
+                cursor.close();
+            }
+        }
+    }
+
+    /**
+     * Synchronously waits for external store to be mounted (eg: SD Card).
+     *
+     * @throws InterruptedException if interrupted
+     * @throws Exception if timed out waiting for SD card to mount
+     */
+    protected void waitForExternalStoreMount() throws Exception {
+        String extStorageState = Environment.getExternalStorageState();
+        int currentWaitTime = 0;
+        while (!extStorageState.equals(Environment.MEDIA_MOUNTED)) {
+            Log.i(LOG_TAG, "Waiting for SD card...");
+            currentWaitTime = timeoutWait(currentWaitTime, DEFAULT_WAIT_POLL_TIME,
+                    DEFAULT_MAX_WAIT_TIME, "Timed out waiting for SD Card to be ready!");
+            extStorageState = Environment.getExternalStorageState();
+        }
+    }
+
+    /**
+     * Synchronously waits for a download to start.
+     *
+     * @param dlRequest the download request id used by Download Manager to track the download.
+     * @throws Exception if timed out while waiting for SD card to mount
+     */
+    protected void waitForDownloadToStart(long dlRequest) throws Exception {
+        Cursor cursor = getCursor(dlRequest);
+        try {
+            int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
+            int value = cursor.getInt(columnIndex);
+            int currentWaitTime = 0;
+
+            while (value != DownloadManager.STATUS_RUNNING &&
+                    (value != DownloadManager.STATUS_FAILED) &&
+                    (value != DownloadManager.STATUS_SUCCESSFUL)) {
+                Log.i(LOG_TAG, "Waiting for download to start...");
+                currentWaitTime = timeoutWait(currentWaitTime, WAIT_FOR_DOWNLOAD_POLL_TIME,
+                        MAX_WAIT_FOR_DOWNLOAD_TIME, "Timed out waiting for download to start!");
+                cursor.requery();
+                assertTrue(cursor.moveToFirst());
+                columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
+                value = cursor.getInt(columnIndex);
+            }
+            assertFalse("Download failed immediately after start",
+                    value == DownloadManager.STATUS_FAILED);
+        } finally {
+            cursor.close();
+        }
+    }
+
+    /**
+     * Synchronously waits for a file to increase in size (such as to monitor that a download is
+     * progressing).
+     *
+     * @param file The file whose size to track.
+     * @throws Exception if timed out while waiting for the file to grow in size.
+     */
+    protected void waitForFileToGrow(File file) throws Exception {
+        int currentWaitTime = 0;
+
+        // File may not even exist yet, so wait until it does (or we timeout)
+        while (!file.exists()) {
+            Log.i(LOG_TAG, "Waiting for file to exist...");
+            currentWaitTime = timeoutWait(currentWaitTime, WAIT_FOR_DOWNLOAD_POLL_TIME,
+                    MAX_WAIT_FOR_DOWNLOAD_TIME, "Timed out waiting for file to be created.");
+        }
+
+        // Get original file size...
+        long originalSize = file.length();
+
+        while (file.length() <= originalSize) {
+            Log.i(LOG_TAG, "Waiting for file to be written to...");
+            currentWaitTime = timeoutWait(currentWaitTime, WAIT_FOR_DOWNLOAD_POLL_TIME,
+                    MAX_WAIT_FOR_DOWNLOAD_TIME, "Timed out waiting for file to be written to.");
+        }
+    }
+
+    /**
+     * Helper to remove all downloads that are registered with the DL Manager.
+     *
+     * Note: This gives us a clean slate b/c it includes downloads that are pending, running,
+     * paused, or have completed.
+     */
+    protected void removeAllCurrentDownloads() {
+        Log.i(LOG_TAG, "Removing all current registered downloads...");
+        Cursor cursor = mDownloadManager.query(new Query());
+        try {
+            if (cursor.moveToFirst()) {
+                do {
+                    int index = cursor.getColumnIndex(DownloadManager.COLUMN_ID);
+                    long downloadId = cursor.getLong(index);
+
+                    mDownloadManager.remove(downloadId);
+                } while (cursor.moveToNext());
+            }
+        } finally {
+            cursor.close();
+        }
+    }
+
+    /**
+     * Helper to perform a standard enqueue of data to the mock server.
+     *
+     * @param body The body to return in the response from the server
+     */
+    protected long doStandardEnqueue(byte[] body) throws Exception {
+        // Prepare the mock server with a standard response
+        enqueueResponse(HTTP_OK, body);
+        return doCommonStandardEnqueue();
+    }
+
+    /**
+     * Helper to perform a standard enqueue of data to the mock server.
+     *
+     * @param body The body to return in the response from the server, contained in the file
+     */
+    protected long doStandardEnqueue(File body) throws Exception {
+        // Prepare the mock server with a standard response
+        enqueueResponse(HTTP_OK, body);
+        return doCommonStandardEnqueue();
+    }
+
+    /**
+     * Helper to do the additional steps (setting title and Uri of default filename) when
+     * doing a standard enqueue request to the server.
+     */
+    protected long doCommonStandardEnqueue() throws Exception {
+        Uri uri = getServerUri(DEFAULT_FILENAME);
+        Request request = new Request(uri);
+        request.setTitle(DEFAULT_FILENAME);
+
+        long dlRequest = mDownloadManager.enqueue(request);
+        Log.i(LOG_TAG, "request ID: " + dlRequest);
+        return dlRequest;
+    }
+
+    /**
+     * Helper to verify an int value in a Cursor
+     *
+     * @param cursor The cursor containing the query results
+     * @param columnName The name of the column to query
+     * @param expected The expected int value
+     */
+    protected void verifyInt(Cursor cursor, String columnName, int expected) {
+        int index = cursor.getColumnIndex(columnName);
+        int actual = cursor.getInt(index);
+        assertEquals(expected, actual);
+    }
+
+    /**
+     * Helper to verify a String value in a Cursor
+     *
+     * @param cursor The cursor containing the query results
+     * @param columnName The name of the column to query
+     * @param expected The expected String value
+     */
+    protected void verifyString(Cursor cursor, String columnName, String expected) {
+        int index = cursor.getColumnIndex(columnName);
+        String actual = cursor.getString(index);
+        Log.i(LOG_TAG, ": " + actual);
+        assertEquals(expected, actual);
+    }
+
+    /**
+     * Performs a query based on ID and returns a Cursor for the query.
+     *
+     * @param id The id of the download in DL Manager; pass -1 to query all downloads
+     * @return A cursor for the query results
+     */
+    protected Cursor getCursor(long id) throws Exception {
+        Query query = new Query();
+        if (id != -1) {
+            query.setFilterById(id);
+        }
+
+        Cursor cursor = mDownloadManager.query(query);
+        int currentWaitTime = 0;
+
+        try {
+            while (!cursor.moveToFirst()) {
+                Thread.sleep(DEFAULT_WAIT_POLL_TIME);
+                currentWaitTime += DEFAULT_WAIT_POLL_TIME;
+                if (currentWaitTime > DEFAULT_MAX_WAIT_TIME) {
+                    fail("timed out waiting for a non-null query result");
+                }
+                cursor.requery();
+            }
+        } catch (Exception e) {
+            cursor.close();
+            throw e;
+        }
+        return cursor;
+    }
+
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/net/DownloadManagerIntegrationTest.java b/core/tests/coretests/src/android/net/DownloadManagerIntegrationTest.java
new file mode 100644
index 0000000..be3cbf7
--- /dev/null
+++ b/core/tests/coretests/src/android/net/DownloadManagerIntegrationTest.java
@@ -0,0 +1,381 @@
+/*
+ * 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 android.net;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.Cursor;
+import android.net.DownloadManager.Query;
+import android.net.DownloadManager.Request;
+import android.net.DownloadManagerBaseTest.DataType;
+import android.net.DownloadManagerBaseTest.MultipleDownloadsCompletedReceiver;
+import android.net.wifi.WifiManager;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Random;
+
+import junit.framework.AssertionFailedError;
+
+import coretestutils.http.MockResponse;
+import coretestutils.http.MockWebServer;
+
+/**
+ * Integration tests of the DownloadManager API.
+ */
+public class DownloadManagerIntegrationTest extends DownloadManagerBaseTest {
+
+    private static String LOG_TAG = "android.net.DownloadManagerIntegrationTest";
+    private static String PROHIBITED_DIRECTORY = "/system";
+    protected MultipleDownloadsCompletedReceiver mReceiver = null;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        setWiFiStateOn(true);
+        mServer.play();
+        removeAllCurrentDownloads();
+        mReceiver = registerNewMultipleDownloadsReceiver();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        setWiFiStateOn(true);
+
+        if (mReceiver != null) {
+            mContext.unregisterReceiver(mReceiver);
+            mReceiver = null;
+            removeAllCurrentDownloads();
+        }
+    }
+
+    /**
+     * Helper that does the actual basic download verification.
+     */
+    protected void doBasicDownload(byte[] blobData) throws Exception {
+        long dlRequest = doStandardEnqueue(blobData);
+
+        // wait for the download to complete
+        waitForDownloadOrTimeout(dlRequest);
+
+        verifyAndCleanupSingleFileDownload(dlRequest, blobData);
+        assertEquals(1, mReceiver.numDownloadsCompleted());
+    }
+
+    /**
+     * Test a basic download of a binary file 500k in size.
+     */
+    @LargeTest
+    public void testBasicBinaryDownload() throws Exception {
+        int fileSize = 500 * 1024;  // 500k
+        byte[] blobData = generateData(fileSize, DataType.BINARY);
+
+        doBasicDownload(blobData);
+    }
+
+    /**
+     * Tests the basic downloading of a text file 300000 bytes in size.
+     */
+    @LargeTest
+    public void testBasicTextDownload() throws Exception {
+        int fileSize = 300000;
+        byte[] blobData = generateData(fileSize, DataType.TEXT);
+
+        doBasicDownload(blobData);
+    }
+
+    /**
+     * Tests when the server drops the connection after all headers (but before any data send).
+     */
+    @LargeTest
+    public void testDropConnection_headers() throws Exception {
+        byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT);
+
+        MockResponse response = enqueueResponse(HTTP_OK, blobData);
+        response.setCloseConnectionAfterHeader("content-length");
+        long dlRequest = doCommonStandardEnqueue();
+
+        // Download will never complete when header is dropped
+        boolean success = waitForDownloadOrTimeoutNoThrow(dlRequest, DEFAULT_WAIT_POLL_TIME,
+                DEFAULT_MAX_WAIT_TIME);
+
+        assertFalse(success);
+    }
+
+    /**
+     * Tests that we get an error code when the server drops the connection during a download.
+     */
+    @LargeTest
+    public void testServerDropConnection_body() throws Exception {
+        byte[] blobData = generateData(25000, DataType.TEXT);  // file size = 25000 bytes
+
+        MockResponse response = enqueueResponse(HTTP_OK, blobData);
+        response.setCloseConnectionAfterXBytes(15382);
+        long dlRequest = doCommonStandardEnqueue();
+        waitForDownloadOrTimeout(dlRequest);
+
+        Cursor cursor = getCursor(dlRequest);
+        try {
+            verifyInt(cursor, DownloadManager.COLUMN_STATUS, DownloadManager.STATUS_FAILED);
+            verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+                    DownloadManager.ERROR_CANNOT_RESUME);
+        } finally {
+            cursor.close();
+        }
+        // Even tho the server drops the connection, we should still get a completed notification
+        assertEquals(1, mReceiver.numDownloadsCompleted());
+    }
+
+    /**
+     * Attempts to download several files simultaneously
+     */
+    @LargeTest
+    public void testMultipleDownloads() throws Exception {
+        // need to be sure all current downloads have stopped first
+        removeAllCurrentDownloads();
+        int NUM_FILES = 50;
+        int MAX_FILE_SIZE = 500 * 1024; // 500 kb
+
+        Random r = new LoggingRng();
+        for (int i=0; i<NUM_FILES; ++i) {
+            int size = r.nextInt(MAX_FILE_SIZE);
+            byte[] blobData = generateData(size, DataType.TEXT);
+
+            Uri uri = getServerUri(DEFAULT_FILENAME);
+            Request request = new Request(uri);
+            request.setTitle(String.format("%s--%d", DEFAULT_FILENAME, i));
+
+            // Prepare the mock server with a standard response
+            enqueueResponse(HTTP_OK, blobData);
+
+            Log.i(LOG_TAG, "request: " + i);
+            mDownloadManager.enqueue(request);
+        }
+
+        waitForDownloadsOrTimeout(WAIT_FOR_DOWNLOAD_POLL_TIME, MAX_WAIT_FOR_DOWNLOAD_TIME);
+        Cursor cursor = mDownloadManager.query(new Query());
+        try {
+            assertEquals(NUM_FILES, cursor.getCount());
+
+            if (cursor.moveToFirst()) {
+                do {
+                    int status = cursor.getInt(cursor.getColumnIndex(
+                            DownloadManager.COLUMN_STATUS));
+                    String filename = cursor.getString(cursor.getColumnIndex(
+                            DownloadManager.COLUMN_URI));
+                    String errorString = String.format(
+                            "File %s failed to download successfully. Status code: %d",
+                            filename, status);
+                    assertEquals(errorString, DownloadManager.STATUS_SUCCESSFUL, status);
+                } while (cursor.moveToNext());
+            }
+
+            assertEquals(NUM_FILES, mReceiver.numDownloadsCompleted());
+        } finally {
+            cursor.close();
+        }
+    }
+
+    /**
+     * Tests trying to download to SD card when the file with same name already exists.
+     */
+    @LargeTest
+    public void testDownloadToExternal_fileExists() throws Exception {
+        File existentFile = createFileOnSD(null, 1, DataType.TEXT, null);
+        byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT);
+
+        // Prepare the mock server with a standard response
+        enqueueResponse(HTTP_OK, blobData);
+
+        try {
+            Uri uri = getServerUri(DEFAULT_FILENAME);
+            Request request = new Request(uri);
+
+            Uri localUri = Uri.fromFile(existentFile);
+            Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath());
+            request.setDestinationUri(localUri);
+
+            long dlRequest = mDownloadManager.enqueue(request);
+
+            // wait for the download to complete
+            waitForDownloadOrTimeout(dlRequest);
+            Cursor cursor = getCursor(dlRequest);
+
+            try {
+                verifyInt(cursor, DownloadManager.COLUMN_STATUS, DownloadManager.STATUS_FAILED);
+                verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+                        DownloadManager.ERROR_FILE_ERROR);
+            } finally {
+                cursor.close();
+            }
+        } finally {
+            existentFile.delete();
+        }
+    }
+
+    /**
+     * Tests trying to download a file to SD card.
+     */
+    @LargeTest
+    public void testDownloadToExternal() throws Exception {
+        String localDownloadDirectory = Environment.getExternalStorageDirectory().getPath();
+        File downloadedFile = new File(localDownloadDirectory, DEFAULT_FILENAME);
+        // make sure the file doesn't already exist in the directory
+        downloadedFile.delete();
+
+        try {
+            byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT);
+
+            // Prepare the mock server with a standard response
+            enqueueResponse(HTTP_OK, blobData);
+
+            Uri uri = getServerUri(DEFAULT_FILENAME);
+            Request request = new Request(uri);
+
+            Uri localUri = Uri.fromFile(downloadedFile);
+            Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath());
+            request.setDestinationUri(localUri);
+
+            long dlRequest = mDownloadManager.enqueue(request);
+
+            // wait for the download to complete
+            waitForDownloadOrTimeout(dlRequest);
+
+            verifyAndCleanupSingleFileDownload(dlRequest, blobData);
+
+            assertEquals(1, mReceiver.numDownloadsCompleted());
+        } finally {
+            downloadedFile.delete();
+        }
+    }
+
+    /**
+     * Tests trying to download a file to the system partition.
+     */
+    @LargeTest
+    public void testDownloadToProhibitedDirectory() throws Exception {
+        File downloadedFile = new File(PROHIBITED_DIRECTORY, DEFAULT_FILENAME);
+        try {
+            byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT);
+
+            // Prepare the mock server with a standard response
+            enqueueResponse(HTTP_OK, blobData);
+
+            Uri uri = getServerUri(DEFAULT_FILENAME);
+            Request request = new Request(uri);
+
+            Uri localUri = Uri.fromFile(downloadedFile);
+            Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath());
+            request.setDestinationUri(localUri);
+
+            try {
+                mDownloadManager.enqueue(request);
+                fail("Failed to throw SecurityException when trying to write to /system.");
+            } catch (SecurityException s) {
+                assertFalse(downloadedFile.exists());
+            }
+        } finally {
+            // Just in case file somehow got created, make sure to delete it
+            downloadedFile.delete();
+        }
+    }
+
+    /**
+     * Tests that a download set for Wifi does not progress while Wifi is disabled, but resumes
+     * once Wifi is re-enabled.
+     */
+    @LargeTest
+    public void testDownloadNoWifi() throws Exception {
+        long timeout = 60 * 1000; // wait only 60 seconds before giving up
+        int fileSize = 140 * 1024;  // 140k
+        byte[] blobData = generateData(fileSize, DataType.TEXT);
+
+        setWiFiStateOn(false);
+        enqueueResponse(HTTP_OK, blobData);
+
+        try {
+            Uri uri = getServerUri(DEFAULT_FILENAME);
+            Request request = new Request(uri);
+            request.setAllowedNetworkTypes(Request.NETWORK_WIFI);
+
+            long dlRequest = mDownloadManager.enqueue(request);
+
+            // wait for the download to complete
+            boolean success = waitForDownloadOrTimeoutNoThrow(dlRequest,
+                    WAIT_FOR_DOWNLOAD_POLL_TIME, timeout);
+            assertFalse("Download proceeded without Wifi connection!", success);
+
+            setWiFiStateOn(true);
+            waitForDownloadOrTimeout(dlRequest);
+
+            assertEquals(1, mReceiver.numDownloadsCompleted());
+        } finally {
+            setWiFiStateOn(true);
+        }
+    }
+
+    /**
+     * Tests trying to download two large files (50M bytes, followed by 60M bytes)
+     */
+    @LargeTest
+    public void testInsufficientSpaceSingleFiles() throws Exception {
+        long fileSize1 = 50000000L;
+        long fileSize2 = 60000000L;
+        File largeFile1 = createFileOnSD(null, fileSize1, DataType.TEXT, null);
+        File largeFile2 = createFileOnSD(null, fileSize2, DataType.TEXT, null);
+
+        try {
+            long dlRequest = doStandardEnqueue(largeFile1);
+            waitForDownloadOrTimeout(dlRequest);
+            ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest);
+            verifyFileContents(pfd, largeFile1);
+            verifyFileSize(pfd, largeFile1.length());
+
+            dlRequest = doStandardEnqueue(largeFile2);
+            waitForDownloadOrTimeout(dlRequest);
+            Cursor cursor = getCursor(dlRequest);
+            try {
+                verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+                        DownloadManager.ERROR_INSUFFICIENT_SPACE);
+            } finally {
+                cursor.close();
+            }
+        } finally {
+            largeFile1.delete();
+            largeFile2.delete();
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/net/DownloadManagerStressTest.java b/core/tests/coretests/src/android/net/DownloadManagerStressTest.java
new file mode 100644
index 0000000..9fa8620
--- /dev/null
+++ b/core/tests/coretests/src/android/net/DownloadManagerStressTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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 android.net;
+
+import java.io.File;
+import java.util.Random;
+
+import android.database.Cursor;
+import android.net.DownloadManager.Query;
+import android.net.DownloadManager.Request;
+import android.os.ParcelFileDescriptor;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
+
+public class DownloadManagerStressTest extends DownloadManagerBaseTest {
+    private static String LOG_TAG = "android.net.DownloadManagerStressTest";
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mServer.play(0);
+        removeAllCurrentDownloads();
+    }
+
+    /**
+     * Attempts to downloading thousands of files simultaneously
+     */
+    public void testDownloadThousands() throws Exception {
+        int NUM_FILES = 1500;
+        int MAX_FILE_SIZE = 3000;
+        long[] reqs = new long[NUM_FILES];
+
+        // need to be sure all current downloads have stopped first
+        MultipleDownloadsCompletedReceiver receiver = registerNewMultipleDownloadsReceiver();
+        Cursor cursor = null;
+        try {
+            Random r = new LoggingRng();
+            for (int i = 0; i < NUM_FILES; ++i) {
+                int size = r.nextInt(MAX_FILE_SIZE);
+                byte[] blobData = generateData(size, DataType.TEXT);
+
+                Uri uri = getServerUri(DEFAULT_FILENAME);
+                Request request = new Request(uri);
+                request.setTitle(String.format("%s--%d", DEFAULT_FILENAME, i));
+
+                // Prepare the mock server with a standard response
+                enqueueResponse(HTTP_OK, blobData);
+
+                Log.i(LOG_TAG, "issuing request: " + i);
+                long reqId = mDownloadManager.enqueue(request);
+                reqs[i] = reqId;
+            }
+
+            // wait for the download to complete or timeout
+            waitForDownloadsOrTimeout(WAIT_FOR_DOWNLOAD_POLL_TIME, MAX_WAIT_FOR_DOWNLOAD_TIME);
+            cursor = mDownloadManager.query(new Query());
+            assertEquals(NUM_FILES, cursor.getCount());
+            Log.i(LOG_TAG, "Verified number of downloads in download manager is what we expect.");
+            while (cursor.moveToNext()) {
+                int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
+                String filename = cursor.getString(cursor.getColumnIndex(
+                        DownloadManager.COLUMN_URI));
+                String errorString = String.format("File %s failed to download successfully. " +
+                        "Status code: %d", filename, status);
+                assertEquals(errorString, DownloadManager.STATUS_SUCCESSFUL, status);
+            }
+            Log.i(LOG_TAG, "Verified each download was successful.");
+            assertEquals(NUM_FILES, receiver.numDownloadsCompleted());
+            Log.i(LOG_TAG, "Verified number of completed downloads in our receiver.");
+
+            // Verify that for each request, we can open the downloaded file
+            for (int i = 0; i < NUM_FILES; ++i) {
+                ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(reqs[i]);
+                pfd.close();
+            }
+            Log.i(LOG_TAG, "Verified we can open each file.");
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+            mContext.unregisterReceiver(receiver);
+            removeAllCurrentDownloads();
+        }
+    }
+
+    /**
+     * Tests trying to download a large file (50M bytes).
+     */
+    public void testDownloadLargeFile() throws Exception {
+        long fileSize = 50000000L;  // note: kept relatively small to not exceed /cache dir size
+        File largeFile = createFileOnSD(null, fileSize, DataType.TEXT, null);
+        MultipleDownloadsCompletedReceiver receiver = registerNewMultipleDownloadsReceiver();
+
+        try {
+            long dlRequest = doStandardEnqueue(largeFile);
+
+             // wait for the download to complete
+            waitForDownloadOrTimeout(dlRequest);
+
+            ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest);
+            verifyFileContents(pfd, largeFile);
+            verifyFileSize(pfd, largeFile.length());
+
+            assertEquals(1, receiver.numDownloadsCompleted());
+            mContext.unregisterReceiver(receiver);
+        } catch (Exception e) {
+            throw e;
+        } finally {
+            largeFile.delete();
+        }
+    }
+
+    /**
+     * Tests trying to download a large file (~300M bytes) when there's not enough space in cache
+     */
+    public void testInsufficientSpace() throws Exception {
+        long fileSize = 300000000L;
+        File largeFile = createFileOnSD(null, fileSize, DataType.TEXT, null);
+
+        Cursor cursor = null;
+        try {
+            long dlRequest = doStandardEnqueue(largeFile);
+
+             // wait for the download to complete
+            waitForDownloadOrTimeout(dlRequest);
+
+            cursor = getCursor(dlRequest);
+            verifyInt(cursor, DownloadManager.COLUMN_STATUS, DownloadManager.STATUS_FAILED);
+            verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE,
+                    DownloadManager.ERROR_INSUFFICIENT_SPACE);
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+            largeFile.delete();
+        }
+    }
+}
diff --git a/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java b/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java
index 38191b0..c5ea9c1 100644
--- a/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java
+++ b/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java
@@ -37,7 +37,9 @@
 import java.io.StringReader;
 import java.lang.Runtime;
 import java.lang.Process;
+import java.util.Hashtable;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -117,18 +119,38 @@
 
     /**
      * Helper method to run tests and return the listener that collected the results.
+     *
+     * For the optional params, pass null to use the default values.
+
      * @param pkgName Android application package for tests
-     * @return the {@link CollectingTestRunListener}
+     * @param className (optional) The class containing the method to test
+     * @param methodName (optional) The method in the class of which to test
+     * @param runnerName (optional) The name of the TestRunner of the test on the device to be run
+     * @param params (optional) Any additional parameters to pass into the Test Runner
      * @throws TimeoutException in case of a timeout on the connection.
      * @throws AdbCommandRejectedException if adb rejects the command
      * @throws ShellCommandUnresponsiveException if the device did not output anything for
      * a period longer than the max time to output.
      * @throws IOException if connection to device was lost.
+     * @return the {@link CollectingTestRunListener}
      */
-    private CollectingTestRunListener doRunTests(String pkgName) throws IOException,
-    TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException {
-        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
-                pkgName, mDevice);
+    private CollectingTestRunListener doRunTests(String pkgName, String className,
+            String methodName, String runnerName, Map<String, String> params) throws IOException,
+            TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException {
+        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(pkgName, runnerName,
+                mDevice);
+
+        if (className != null && methodName != null) {
+            testRunner.setMethodName(className, methodName);
+        }
+
+        // Add in any additional args to pass into the test
+        if (params != null) {
+            for (Entry<String, String> argPair : params.entrySet()) {
+                testRunner.addInstrumentationArg(argPair.getKey(), argPair.getValue());
+            }
+        }
+
         CollectingTestRunListener listener = new CollectingTestRunListener();
         try {
             testRunner.run(listener);
@@ -142,16 +164,34 @@
      * Runs the specified packages tests, and returns whether all tests passed or not.
      *
      * @param pkgName Android application package for tests
-     * @return true if every test passed, false otherwise.
+     * @param className The class containing the method to test
+     * @param methodName The method in the class of which to test
+     * @param runnerName The name of the TestRunner of the test on the device to be run
+     * @param params Any additional parameters to pass into the Test Runner
+     * @return true if test passed, false otherwise.
+     */
+    public boolean runDeviceTestsDidAllTestsPass(String pkgName, String className,
+            String methodName, String runnerName, Map<String, String> params) throws IOException,
+            TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException {
+        CollectingTestRunListener listener = doRunTests(pkgName, className, methodName,
+                runnerName, params);
+        return listener.didAllTestsPass();
+    }
+
+    /**
+     * Runs the specified packages tests, and returns whether all tests passed or not.
+     *
+     * @param pkgName Android application package for tests
      * @throws TimeoutException in case of a timeout on the connection.
      * @throws AdbCommandRejectedException if adb rejects the command
      * @throws ShellCommandUnresponsiveException if the device did not output anything for
      * a period longer than the max time to output.
      * @throws IOException if connection to device was lost.
+     * @return true if every test passed, false otherwise.
      */
     public boolean runDeviceTestsDidAllTestsPass(String pkgName) throws IOException,
             TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException {
-        CollectingTestRunListener listener = doRunTests(pkgName);
+        CollectingTestRunListener listener = doRunTests(pkgName, null, null, null, null);
         return listener.didAllTestsPass();
     }
 
@@ -535,7 +575,7 @@
     }
 
     // For collecting results from running device tests
-    private static class CollectingTestRunListener implements ITestRunListener {
+    public static class CollectingTestRunListener implements ITestRunListener {
 
         private boolean mAllTestsPassed = true;
         private String mTestRunErrorMessage = null;
diff --git a/core/tests/hosttests/src/android/net/DownloadManagerHostTests.java b/core/tests/hosttests/src/android/net/DownloadManagerHostTests.java
new file mode 100644
index 0000000..cfabb6c
--- /dev/null
+++ b/core/tests/hosttests/src/android/net/DownloadManagerHostTests.java
@@ -0,0 +1,193 @@
+/*
+ * 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 android.net;
+
+import android.content.pm.PackageManagerHostTestUtils;
+
+import com.android.ddmlib.Log;
+import com.android.hosttest.DeviceTestCase;
+import com.android.hosttest.DeviceTestSuite;
+
+import java.io.File;
+import java.util.Hashtable;
+
+import junit.framework.Test;
+
+/**
+ * Host-based tests of the DownloadManager API. (Uses a device-based app to actually invoke the
+ * various tests.)
+ */
+public class DownloadManagerHostTests extends DeviceTestCase {
+    protected PackageManagerHostTestUtils mPMUtils = null;
+
+    private static final String LOG_TAG = "android.net.DownloadManagerHostTests";
+    private static final String FILE_DOWNLOAD_APK = "DownloadManagerTestApp.apk";
+    private static final String FILE_DOWNLOAD_PKG = "com.android.frameworks.downloadmanagertests";
+    private static final String FILE_DOWNLOAD_CLASS =
+            "com.android.frameworks.downloadmanagertests.DownloadManagerTestApp";
+    private static final String DOWNLOAD_TEST_RUNNER_NAME =
+            "com.android.frameworks.downloadmanagertests.DownloadManagerTestRunner";
+
+    // Extra parameters to pass to the TestRunner
+    private static final String EXTERNAL_DOWNLOAD_URI_KEY = "external_download_uri";
+    // Note: External environment variable ANDROID_TEST_EXTERNAL_URI must be set to point to the
+    // external URI under which the files downloaded by the tests can be found. Note that the Uri
+    // must be accessible by the device during a test run.
+    private static String EXTERNAL_DOWNLOAD_URI_VALUE = null;
+
+    Hashtable<String, String> mExtraParams = null;
+
+    public static Test suite() {
+        return new DeviceTestSuite(DownloadManagerHostTests.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        // ensure apk path has been set before test is run
+        assertNotNull(getTestAppPath());
+        mPMUtils = new PackageManagerHostTestUtils(getDevice());
+        EXTERNAL_DOWNLOAD_URI_VALUE = System.getenv("ANDROID_TEST_EXTERNAL_URI");
+        assertNotNull(EXTERNAL_DOWNLOAD_URI_VALUE);
+        mExtraParams = getExtraParams();
+    }
+
+    /**
+     * Helper function to get extra params that can be used to pass into the helper app.
+     */
+    protected Hashtable<String, String> getExtraParams() {
+        Hashtable<String, String> extraParams = new Hashtable<String, String>();
+        extraParams.put(EXTERNAL_DOWNLOAD_URI_KEY, EXTERNAL_DOWNLOAD_URI_VALUE);
+        return extraParams;
+    }
+
+    /**
+     * Tests that a large download over WiFi
+     * @throws Exception if the test failed at any point
+     */
+    public void testLargeDownloadOverWiFi() throws Exception {
+        mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(),
+                File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true);
+
+        boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+                FILE_DOWNLOAD_CLASS, "runLargeDownloadOverWiFi", DOWNLOAD_TEST_RUNNER_NAME,
+                mExtraParams);
+
+        assertTrue("Failed to install large file over WiFi in < 10 minutes!", testPassed);
+    }
+
+    /**
+     * Spawns a device-based function to initiate a download on the device, reboots the device,
+     * then waits and verifies the download succeeded.
+     *
+     * @throws Exception if the test failed at any point
+     */
+    public void testDownloadManagerSingleReboot() throws Exception {
+        mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(),
+                File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true);
+
+        boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+                FILE_DOWNLOAD_CLASS, "initiateDownload", DOWNLOAD_TEST_RUNNER_NAME,
+                mExtraParams);
+
+        assertTrue("Failed to initiate download properly!", testPassed);
+        mPMUtils.rebootDevice();
+        testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+                FILE_DOWNLOAD_CLASS, "verifyFileDownloadSucceeded", DOWNLOAD_TEST_RUNNER_NAME,
+                mExtraParams);
+        assertTrue("Failed to verify initiated download completed properyly!", testPassed);
+    }
+
+    /**
+     * Spawns a device-based function to initiate a download on the device, reboots the device three
+     * times (using different intervals), then waits and verifies the download succeeded.
+     *
+     * @throws Exception if the test failed at any point
+     */
+    public void testDownloadManagerMultipleReboots() throws Exception {
+        mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(),
+                File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true);
+
+        boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+                FILE_DOWNLOAD_CLASS, "initiateDownload", DOWNLOAD_TEST_RUNNER_NAME,
+                mExtraParams);
+
+        assertTrue("Failed to initiate download properly!", testPassed);
+        Thread.sleep(5000);
+
+        // Do 3 random reboots - after 13, 9, and 19 seconds
+        Log.i(LOG_TAG, "First reboot...");
+        mPMUtils.rebootDevice();
+        Thread.sleep(13000);
+        Log.i(LOG_TAG, "Second reboot...");
+        mPMUtils.rebootDevice();
+        Thread.sleep(9000);
+        Log.i(LOG_TAG, "Third reboot...");
+        mPMUtils.rebootDevice();
+        Thread.sleep(19000);
+        testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+                FILE_DOWNLOAD_CLASS, "verifyFileDownloadSucceeded", DOWNLOAD_TEST_RUNNER_NAME,
+                mExtraParams);
+        assertTrue("Failed to verify initiated download completed properyly!", testPassed);
+    }
+
+    /**
+     * Spawns a device-based function to test download while WiFi is enabled/disabled multiple times
+     * during the download.
+     *
+     * @throws Exception if the test failed at any point
+     */
+    public void testDownloadMultipleWiFiEnableDisable() throws Exception {
+        mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(),
+                File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true);
+
+        boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+                FILE_DOWNLOAD_CLASS, "runDownloadMultipleWiFiEnableDisable",
+                DOWNLOAD_TEST_RUNNER_NAME, mExtraParams);
+        assertTrue(testPassed);
+    }
+
+    /**
+     * Spawns a device-based function to test switching on/off both airplane mode and WiFi
+     *
+     * @throws Exception if the test failed at any point
+     */
+    public void testDownloadMultipleSwitching() throws Exception {
+        mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(),
+                File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true);
+
+        boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+                FILE_DOWNLOAD_CLASS, "runDownloadMultipleSwitching",
+                DOWNLOAD_TEST_RUNNER_NAME, mExtraParams);
+        assertTrue(testPassed);
+    }
+
+    /**
+     * Spawns a device-based function to test switching on/off airplane mode multiple times
+     *
+     * @throws Exception if the test failed at any point
+     */
+    public void testDownloadMultipleAirplaneModeEnableDisable() throws Exception {
+        mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(),
+                File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true);
+
+        boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG,
+                FILE_DOWNLOAD_CLASS, "runDownloadMultipleAirplaneModeEnableDisable",
+                DOWNLOAD_TEST_RUNNER_NAME, mExtraParams);
+        assertTrue(testPassed);
+    }
+}
diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk b/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk
new file mode 100644
index 0000000..576765c
--- /dev/null
+++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk
@@ -0,0 +1,29 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+				   ../../../coretests/src/android/net/DownloadManagerBaseTest.java
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-common frameworks-core-util-lib
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := DownloadManagerTestApp
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/AndroidManifest.xml b/core/tests/hosttests/test-apps/DownloadManagerTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..3f2be3c
--- /dev/null
+++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?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.frameworks.downloadmanagertests">
+
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+
+    <application android:label="DownloadManagerTestApp">
+            <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+            android:name=".DownloadManagerTestRunner"
+            android:targetPackage="com.android.frameworks.downloadmanagertests"
+            android:label="Frameworks Download Manager Test App" />
+
+</manifest>
diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestApp.java b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestApp.java
new file mode 100644
index 0000000..ef81353
--- /dev/null
+++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestApp.java
@@ -0,0 +1,463 @@
+/*
+ * 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.frameworks.downloadmanagertests;
+
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.DownloadManager;
+import android.net.DownloadManager.Query;
+import android.net.DownloadManager.Request;
+import android.net.DownloadManagerBaseTest;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.provider.Settings;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+
+import coretestutils.http.MockResponse;
+import coretestutils.http.MockWebServer;
+import coretestutils.http.RecordedRequest;
+
+/**
+ * Class to test downloading files from a real (not mock) external server.
+ */
+public class DownloadManagerTestApp extends DownloadManagerBaseTest {
+    protected static String DOWNLOAD_STARTED_FLAG = "DMTEST_DOWNLOAD_STARTED";
+    protected static String LOG_TAG =
+            "com.android.frameworks.downloadmanagertests.DownloadManagerTestApp";
+
+    protected static String DOWNLOAD_500K_FILENAME = "External541kb.apk";
+    protected static long DOWNLOAD_500K_FILESIZE = 570927;
+    protected static String DOWNLOAD_1MB_FILENAME = "External1mb.apk";
+    protected static long DOWNLOAD_1MB_FILESIZE = 1041262;
+    protected static String DOWNLOAD_10MB_FILENAME = "External10mb.apk";
+    protected static long DOWNLOAD_10MB_FILESIZE = 10258741;
+
+    // Values to be obtained from TestRunner
+    private String externalDownloadUriValue = null;
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        DownloadManagerTestRunner mRunner = (DownloadManagerTestRunner)getInstrumentation();
+        externalDownloadUriValue = mRunner.externalDownloadUriValue;
+        assertNotNull(externalDownloadUriValue);
+
+        if (!externalDownloadUriValue.endsWith("/")) {
+            externalDownloadUriValue += "/";
+        }
+    }
+
+    /**
+     * Gets the external URL of the file to download
+     *
+     * @return the Uri of the external file to download
+     */
+    private Uri getExternalFileUri(String file) {
+        return Uri.parse(externalDownloadUriValue + file);
+    }
+
+    /**
+     * Gets the path to the file that flags that a download has started. The file contains the
+     * DownloadManager id of the download being trackted between reboot sessions.
+     *
+     * @return The path of the file tracking that a download has started
+     * @throws InterruptedException if interrupted
+     * @throws Exception if timed out while waiting for SD card to mount
+     */
+    protected String getDownloadStartedFilePath() {
+        String path = Environment.getExternalStorageDirectory().getPath();
+        return path + File.separatorChar + DOWNLOAD_STARTED_FLAG;
+    }
+
+    /**
+     * Common setup steps for downloads.
+     *
+     * Note that these are not included in setUp, so that individual tests can control their own
+     * state between reboots, etc.
+     */
+    protected void doCommonDownloadSetup() throws Exception {
+        setWiFiStateOn(true);
+        setAirplaneModeOn(false);
+        waitForExternalStoreMount();
+        removeAllCurrentDownloads();
+    }
+
+    /**
+     * Initiates a download.
+     *
+     * Queues up a download to the download manager, and saves the DownloadManager's assigned
+     * download ID for this download to a file.
+     *
+     * @throws Exception if unsuccessful
+     */
+    public void initiateDownload() throws Exception {
+        String filename = DOWNLOAD_1MB_FILENAME;
+        mContext.deleteFile(DOWNLOAD_STARTED_FLAG);
+        FileOutputStream fileOutput = mContext.openFileOutput(DOWNLOAD_STARTED_FLAG, 0);
+        DataOutputStream outputFile = null;
+        doCommonDownloadSetup();
+
+        try {
+            long dlRequest = -1;
+
+            // Make sure there are no pending downloads currently going on
+            removeAllCurrentDownloads();
+
+            Uri remoteUri = getExternalFileUri(filename);
+            Request request = new Request(remoteUri);
+
+            dlRequest = mDownloadManager.enqueue(request);
+            waitForDownloadToStart(dlRequest);
+            assertTrue(dlRequest != -1);
+
+            // Store ID of download for later retrieval
+            outputFile = new DataOutputStream(fileOutput);
+            outputFile.writeLong(dlRequest);
+        } finally {
+            if (outputFile != null) {
+                outputFile.flush();
+                outputFile.close();
+            }
+        }
+    }
+
+    /**
+     * Waits for a previously-initiated download and verifies it has completed successfully.
+     *
+     * @throws Exception if unsuccessful
+     */
+    public void verifyFileDownloadSucceeded() throws Exception {
+        String filename = DOWNLOAD_1MB_FILENAME;
+        long filesize = DOWNLOAD_1MB_FILESIZE;
+        long dlRequest = -1;
+        boolean rebootMarkerValid = false;
+        DataInputStream dataInputFile = null;
+
+        setWiFiStateOn(true);
+        setAirplaneModeOn(false);
+
+        try {
+            FileInputStream inFile = mContext.openFileInput(DOWNLOAD_STARTED_FLAG);
+            dataInputFile = new DataInputStream(inFile);
+            dlRequest = dataInputFile.readLong();
+        } catch (Exception e) {
+            // The file was't valid so we just leave the flag false
+            Log.i(LOG_TAG, "Unable to determine initial download id.");
+            throw e;
+        } finally {
+            if (dataInputFile != null) {
+                dataInputFile.close();
+            }
+            mContext.deleteFile(DOWNLOAD_STARTED_FLAG);
+        }
+
+        assertTrue(dlRequest != -1);
+        Cursor cursor = getCursor(dlRequest);
+        ParcelFileDescriptor pfd = null;
+        try {
+            assertTrue("Unable to query last initiated download!", cursor.moveToFirst());
+
+            int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
+            int status = cursor.getInt(columnIndex);
+            int currentWaitTime = 0;
+
+            // Wait until the download finishes
+            waitForDownloadOrTimeout(dlRequest);
+
+            Log.i(LOG_TAG, "Verifying download information...");
+            // Verify specific info about the file (size, name, etc)...
+            pfd = mDownloadManager.openDownloadedFile(dlRequest);
+            verifyFileSize(pfd, filesize);
+        } catch (Exception e) {
+            Log.i(LOG_TAG, "error: " + e.toString());
+            throw e;
+        } finally {
+            // Clean up...
+            cursor.close();
+            mDownloadManager.remove(dlRequest);
+            if (pfd != null) {
+                pfd.close();
+            }
+        }
+    }
+
+    /**
+     * Tests downloading a large file over WiFi (~10 Mb).
+     *
+     * @throws Exception if unsuccessful
+     */
+    public void runLargeDownloadOverWiFi() throws Exception {
+        String filename = DOWNLOAD_10MB_FILENAME;
+        long filesize = DOWNLOAD_10MB_FILESIZE;
+        long dlRequest = -1;
+        doCommonDownloadSetup();
+
+        // Make sure there are no pending downloads currently going on
+        removeAllCurrentDownloads();
+
+        Uri remoteUri = getExternalFileUri(filename);
+        Request request = new Request(remoteUri);
+        request.setMediaType(getMimeMapping(DownloadFileType.APK));
+
+        dlRequest = mDownloadManager.enqueue(request);
+
+        // Rather large file, so wait up to 15 mins...
+        waitForDownloadOrTimeout(dlRequest, WAIT_FOR_DOWNLOAD_POLL_TIME, 15 * 60 * 1000);
+
+        Cursor cursor = getCursor(dlRequest);
+        ParcelFileDescriptor pfd = null;
+        try {
+            Log.i(LOG_TAG, "Verifying download information...");
+            // Verify specific info about the file (size, name, etc)...
+            pfd = mDownloadManager.openDownloadedFile(dlRequest);
+            verifyFileSize(pfd, filesize);
+        } finally {
+            if (pfd != null) {
+                pfd.close();
+            }
+            mDownloadManager.remove(dlRequest);
+            cursor.close();
+        }
+    }
+
+    /**
+     * Tests that downloads resume when switching back and forth from having connectivity to
+     * having no connectivity using both WiFi and airplane mode.
+     *
+     * Note: Device has no mobile access when running this test.
+     *
+     * @throws Exception if unsuccessful
+     */
+    public void runDownloadMultipleSwitching() throws Exception {
+        String filename = DOWNLOAD_500K_FILENAME;
+        long filesize = DOWNLOAD_500K_FILESIZE;
+        doCommonDownloadSetup();
+
+        String localDownloadDirectory = Environment.getExternalStorageDirectory().getPath();
+        File downloadedFile = new File(localDownloadDirectory, filename);
+
+        long dlRequest = -1;
+        try {
+            downloadedFile.delete();
+
+            // Make sure there are no pending downloads currently going on
+            removeAllCurrentDownloads();
+
+            Uri remoteUri = getExternalFileUri(filename);
+            Request request = new Request(remoteUri);
+
+            // Local destination of downloaded file
+            Uri localUri = Uri.fromFile(downloadedFile);
+            Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath());
+            request.setDestinationUri(localUri);
+
+            request.setAllowedNetworkTypes(Request.NETWORK_MOBILE | Request.NETWORK_WIFI);
+
+            dlRequest = mDownloadManager.enqueue(request);
+            waitForDownloadToStart(dlRequest);
+            // make sure we're starting to download some data...
+            waitForFileToGrow(downloadedFile);
+
+            // download disable
+            setWiFiStateOn(false);
+
+            // download disable
+            Log.i(LOG_TAG, "Turning on airplane mode...");
+            setAirplaneModeOn(true);
+            Thread.sleep(30 * 1000);  // wait 30 secs
+
+            // download disable
+            setWiFiStateOn(true);
+            Thread.sleep(30 * 1000);  // wait 30 secs
+
+            // download enable
+            Log.i(LOG_TAG, "Turning off airplane mode...");
+            setAirplaneModeOn(false);
+            Thread.sleep(5 * 1000);  // wait 5 seconds
+
+            // download disable
+            Log.i(LOG_TAG, "Turning off WiFi...");
+            setWiFiStateOn(false);
+            Thread.sleep(30 * 1000);  // wait 30 secs
+
+            // finally, turn WiFi back on and finish up the download
+            Log.i(LOG_TAG, "Turning on WiFi...");
+            setWiFiStateOn(true);
+            Log.i(LOG_TAG, "Waiting up to 3 minutes for download to complete...");
+            waitForDownloadsOrTimeout(dlRequest, 3 * 60 * 1000);
+            ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest);
+            verifyFileSize(pfd, filesize);
+        } finally {
+            Log.i(LOG_TAG, "Cleaning up files...");
+            if (dlRequest != -1) {
+                mDownloadManager.remove(dlRequest);
+            }
+            downloadedFile.delete();
+        }
+    }
+
+    /**
+     * Tests that downloads resume when switching on/off WiFi at various intervals.
+     *
+     * Note: Device has no mobile access when running this test.
+     *
+     * @throws Exception if unsuccessful
+     */
+    public void runDownloadMultipleWiFiEnableDisable() throws Exception {
+        String filename = DOWNLOAD_500K_FILENAME;
+        long filesize = DOWNLOAD_500K_FILESIZE;
+        doCommonDownloadSetup();
+
+        String localDownloadDirectory = Environment.getExternalStorageDirectory().getPath();
+        File downloadedFile = new File(localDownloadDirectory, filename);
+        long dlRequest = -1;
+        try {
+            downloadedFile.delete();
+
+            // Make sure there are no pending downloads currently going on
+            removeAllCurrentDownloads();
+
+            Uri remoteUri = getExternalFileUri(filename);
+            Request request = new Request(remoteUri);
+
+            // Local destination of downloaded file
+            Uri localUri = Uri.fromFile(downloadedFile);
+            Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath());
+            request.setDestinationUri(localUri);
+
+            request.setAllowedNetworkTypes(Request.NETWORK_WIFI);
+
+            dlRequest = mDownloadManager.enqueue(request);
+            waitForDownloadToStart(dlRequest);
+            // are we making any progress?
+            waitForFileToGrow(downloadedFile);
+
+            // download disable
+            Log.i(LOG_TAG, "Turning off WiFi...");
+            setWiFiStateOn(false);
+            Thread.sleep(40 * 1000);  // wait 40 seconds
+
+            // enable download...
+            Log.i(LOG_TAG, "Turning on WiFi again...");
+            setWiFiStateOn(true);
+            waitForFileToGrow(downloadedFile);
+
+            // download disable
+            Log.i(LOG_TAG, "Turning off WiFi...");
+            setWiFiStateOn(false);
+            Thread.sleep(20 * 1000);  // wait 20 seconds
+
+            // enable download...
+            Log.i(LOG_TAG, "Turning on WiFi again...");
+            setWiFiStateOn(true);
+
+            Log.i(LOG_TAG, "Waiting up to 3 minutes for download to complete...");
+            waitForDownloadsOrTimeout(dlRequest, 3 * 60 * 1000);
+            ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest);
+            verifyFileSize(pfd, filesize);
+        } finally {
+            Log.i(LOG_TAG, "Cleaning up files...");
+            if (dlRequest != -1) {
+                mDownloadManager.remove(dlRequest);
+            }
+            downloadedFile.delete();
+        }
+    }
+
+    /**
+     * Tests that downloads resume when switching on/off Airplane mode numerous times at
+     * various intervals.
+     *
+     * Note: Device has no mobile access when running this test.
+     *
+     * @throws Exception if unsuccessful
+     */
+    public void runDownloadMultipleAirplaneModeEnableDisable() throws Exception {
+        String filename = DOWNLOAD_500K_FILENAME;
+        long filesize = DOWNLOAD_500K_FILESIZE;
+        // make sure WiFi is enabled, and airplane mode is not on
+        doCommonDownloadSetup();
+
+        String localDownloadDirectory = Environment.getExternalStorageDirectory().getPath();
+        File downloadedFile = new File(localDownloadDirectory, filename);
+        long dlRequest = -1;
+        try {
+            downloadedFile.delete();
+
+            // Make sure there are no pending downloads currently going on
+            removeAllCurrentDownloads();
+
+            Uri remoteUri = getExternalFileUri(filename);
+            Request request = new Request(remoteUri);
+
+            // Local destination of downloaded file
+            Uri localUri = Uri.fromFile(downloadedFile);
+            Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath());
+            request.setDestinationUri(localUri);
+
+            request.setAllowedNetworkTypes(Request.NETWORK_WIFI);
+
+            dlRequest = mDownloadManager.enqueue(request);
+            waitForDownloadToStart(dlRequest);
+            // are we making any progress?
+            waitForFileToGrow(downloadedFile);
+
+            // download disable
+            Log.i(LOG_TAG, "Turning on Airplane mode...");
+            setAirplaneModeOn(true);
+            Thread.sleep(60 * 1000);  // wait 1 minute
+
+            // download enable
+            Log.i(LOG_TAG, "Turning off Airplane mode...");
+            setAirplaneModeOn(false);
+            // make sure we're starting to download some data...
+            waitForFileToGrow(downloadedFile);
+
+            // reenable the connection to start up the download again
+            Log.i(LOG_TAG, "Turning on Airplane mode again...");
+            setAirplaneModeOn(true);
+            Thread.sleep(20 * 1000);  // wait 20 seconds
+
+            // Finish up the download...
+            Log.i(LOG_TAG, "Turning off Airplane mode again...");
+            setAirplaneModeOn(false);
+
+            Log.i(LOG_TAG, "Waiting up to 3 minutes for donwload to complete...");
+            waitForDownloadsOrTimeout(dlRequest, 180 * 1000);  // wait up to 3 mins before timeout
+            ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest);
+            verifyFileSize(pfd, filesize);
+        } finally {
+            Log.i(LOG_TAG, "Cleaning up files...");
+            if (dlRequest != -1) {
+                mDownloadManager.remove(dlRequest);
+            }
+            downloadedFile.delete();
+        }
+    }
+}
diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestRunner.java b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestRunner.java
new file mode 100644
index 0000000..0f16619
--- /dev/null
+++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestRunner.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.frameworks.downloadmanagertests;
+
+import android.os.Bundle;
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+import android.util.Log;
+
+import com.android.frameworks.downloadmanagertests.DownloadManagerTestApp;
+
+import junit.framework.TestSuite;
+
+/**
+ * Instrumentation Test Runner for all download manager tests.
+ *
+ * To run the download manager tests:
+ *
+ * adb shell am instrument -e external_download_1mb_uri <uri> external_download_500k_uri <uri> \
+ *     -w com.android.frameworks.downloadmanagertests/.DownloadManagerTestRunner
+ */
+
+public class DownloadManagerTestRunner extends InstrumentationTestRunner {
+    private static final String EXTERNAL_DOWNLOAD_URI_KEY = "external_download_uri";
+    public String externalDownloadUriValue = null;
+
+    @Override
+    public TestSuite getAllTests() {
+        TestSuite suite = new InstrumentationTestSuite(this);
+        suite.addTestSuite(DownloadManagerTestApp.class);
+        return suite;
+    }
+
+    @Override
+    public ClassLoader getLoader() {
+        return DownloadManagerTestRunner.class.getClassLoader();
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        // Extract the extra params passed in from the bundle...
+        String externalDownloadUri = (String) icicle.get(EXTERNAL_DOWNLOAD_URI_KEY);
+        if (externalDownloadUri != null) {
+            externalDownloadUriValue = externalDownloadUri;
+        }
+        super.onCreate(icicle);
+    }
+
+}
diff --git a/core/tests/utillib/Android.mk b/core/tests/utillib/Android.mk
new file mode 100644
index 0000000..299ea5a
--- /dev/null
+++ b/core/tests/utillib/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_MODULE := frameworks-core-util-lib
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Build the test APKs using their own makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
diff --git a/core/tests/utillib/src/coretestutils/http/MockResponse.java b/core/tests/utillib/src/coretestutils/http/MockResponse.java
new file mode 100644
index 0000000..5b03e5f
--- /dev/null
+++ b/core/tests/utillib/src/coretestutils/http/MockResponse.java
@@ -0,0 +1,239 @@
+/*
+ * 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 coretestutils.http;
+
+import static coretestutils.http.MockWebServer.ASCII;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import android.util.Log;
+
+/**
+ * A scripted response to be replayed by the mock web server.
+ */
+public class MockResponse {
+    private static final byte[] EMPTY_BODY = new byte[0];
+    static final String LOG_TAG = "coretestutils.http.MockResponse";
+
+    private String status = "HTTP/1.1 200 OK";
+    private Map<String, String> headers = new HashMap<String, String>();
+    private byte[] body = EMPTY_BODY;
+    private boolean closeConnectionAfter = false;
+    private String closeConnectionAfterHeader = null;
+    private int closeConnectionAfterXBytes = -1;
+    private int pauseConnectionAfterXBytes = -1;
+    private File bodyExternalFile = null;
+
+    public MockResponse() {
+        addHeader("Content-Length", 0);
+    }
+
+    /**
+     * Returns the HTTP response line, such as "HTTP/1.1 200 OK".
+     */
+    public String getStatus() {
+        return status;
+    }
+
+    public MockResponse setResponseCode(int code) {
+        this.status = "HTTP/1.1 " + code + " OK";
+        return this;
+    }
+
+    /**
+     * Returns the HTTP headers, such as "Content-Length: 0".
+     */
+    public List<String> getHeaders() {
+        List<String> headerStrings = new ArrayList<String>();
+        for (String header : headers.keySet()) {
+            headerStrings.add(header + ": " + headers.get(header));
+        }
+        return headerStrings;
+    }
+
+    public MockResponse addHeader(String header, String value) {
+        headers.put(header.toLowerCase(), value);
+        return this;
+    }
+
+    public MockResponse addHeader(String header, long value) {
+        return addHeader(header, Long.toString(value));
+    }
+
+    public MockResponse removeHeader(String header) {
+        headers.remove(header.toLowerCase());
+        return this;
+    }
+
+    /**
+     * Returns true if the body should come from an external file, false otherwise.
+     */
+    private boolean bodyIsExternal() {
+        return bodyExternalFile != null;
+    }
+
+    /**
+     * Returns an input stream containing the raw HTTP payload.
+     */
+    public InputStream getBody() {
+        if (bodyIsExternal()) {
+            try {
+                return new FileInputStream(bodyExternalFile);
+            } catch (FileNotFoundException e) {
+                Log.e(LOG_TAG, "File not found: " + bodyExternalFile.getAbsolutePath());
+            }
+        }
+        return new ByteArrayInputStream(this.body);
+    }
+
+    public MockResponse setBody(File body) {
+        addHeader("Content-Length", body.length());
+        this.bodyExternalFile = body;
+        return this;
+    }
+
+    public MockResponse setBody(byte[] body) {
+        addHeader("Content-Length", body.length);
+        this.body = body;
+        return this;
+    }
+
+    public MockResponse setBody(String body) {
+        try {
+            return setBody(body.getBytes(ASCII));
+        } catch (UnsupportedEncodingException e) {
+            throw new AssertionError();
+        }
+    }
+
+    /**
+     * Sets the body as chunked.
+     *
+     * Currently chunked body is not supported for external files as bodies.
+     */
+    public MockResponse setChunkedBody(byte[] body, int maxChunkSize) throws IOException {
+        addHeader("Transfer-encoding", "chunked");
+
+        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
+        int pos = 0;
+        while (pos < body.length) {
+            int chunkSize = Math.min(body.length - pos, maxChunkSize);
+            bytesOut.write(Integer.toHexString(chunkSize).getBytes(ASCII));
+            bytesOut.write("\r\n".getBytes(ASCII));
+            bytesOut.write(body, pos, chunkSize);
+            bytesOut.write("\r\n".getBytes(ASCII));
+            pos += chunkSize;
+        }
+        bytesOut.write("0\r\n".getBytes(ASCII));
+        this.body = bytesOut.toByteArray();
+        return this;
+    }
+
+    public MockResponse setChunkedBody(String body, int maxChunkSize) throws IOException {
+        return setChunkedBody(body.getBytes(ASCII), maxChunkSize);
+    }
+
+    @Override public String toString() {
+        return status;
+    }
+
+    public boolean shouldCloseConnectionAfter() {
+        return closeConnectionAfter;
+    }
+
+    public MockResponse setCloseConnectionAfter(boolean closeConnectionAfter) {
+        this.closeConnectionAfter = closeConnectionAfter;
+        return this;
+    }
+
+    /**
+     * Sets the header after which sending the server should close the connection.
+     */
+    public MockResponse setCloseConnectionAfterHeader(String header) {
+        closeConnectionAfterHeader = header;
+        setCloseConnectionAfter(true);
+        return this;
+    }
+
+    /**
+     * Returns the header after which sending the server should close the connection.
+     */
+    public String getCloseConnectionAfterHeader() {
+        return closeConnectionAfterHeader;
+    }
+
+    /**
+     * Sets the number of bytes in the body to send before which the server should close the
+     * connection. Set to -1 to unset and send the entire body (default).
+     */
+    public MockResponse setCloseConnectionAfterXBytes(int position) {
+        closeConnectionAfterXBytes = position;
+        setCloseConnectionAfter(true);
+        return this;
+    }
+
+    /**
+     * Returns the number of bytes in the body to send before which the server should close the
+     * connection. Returns -1 if the entire body should be sent (default).
+     */
+    public int getCloseConnectionAfterXBytes() {
+        return closeConnectionAfterXBytes;
+    }
+
+    /**
+     * Sets the number of bytes in the body to send before which the server should pause the
+     * connection (stalls in sending data). Only one pause per response is supported.
+     * Set to -1 to unset pausing (default).
+     */
+    public MockResponse setPauseConnectionAfterXBytes(int position) {
+        pauseConnectionAfterXBytes = position;
+        return this;
+    }
+
+    /**
+     * Returns the number of bytes in the body to send before which the server should pause the
+     * connection (stalls in sending data). (Returns -1 if it should not pause).
+     */
+    public int getPauseConnectionAfterXBytes() {
+        return pauseConnectionAfterXBytes;
+    }
+
+    /**
+     * Returns true if this response is flagged to pause the connection mid-stream, false otherwise
+     */
+    public boolean getShouldPause() {
+        return (pauseConnectionAfterXBytes != -1);
+    }
+
+    /**
+     * Returns true if this response is flagged to close the connection mid-stream, false otherwise
+     */
+    public boolean getShouldClose() {
+        return (closeConnectionAfterXBytes != -1);
+    }
+}
diff --git a/core/tests/utillib/src/coretestutils/http/MockWebServer.java b/core/tests/utillib/src/coretestutils/http/MockWebServer.java
new file mode 100644
index 0000000..c329ffa
--- /dev/null
+++ b/core/tests/utillib/src/coretestutils/http/MockWebServer.java
@@ -0,0 +1,426 @@
+/*
+ * 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 coretestutils.http;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import android.util.Log;
+
+/**
+ * A scriptable web server. Callers supply canned responses and the server
+ * replays them upon request in sequence.
+ *
+ * TODO: merge with the version from libcore/support/src/tests/java once it's in.
+ */
+public final class MockWebServer {
+    static final String ASCII = "US-ASCII";
+    static final String LOG_TAG = "coretestutils.http.MockWebServer";
+
+    private final BlockingQueue<RecordedRequest> requestQueue
+            = new LinkedBlockingQueue<RecordedRequest>();
+    private final BlockingQueue<MockResponse> responseQueue
+            = new LinkedBlockingQueue<MockResponse>();
+    private int bodyLimit = Integer.MAX_VALUE;
+    private final ExecutorService executor = Executors.newCachedThreadPool();
+    // keep Futures around so we can rethrow any exceptions thrown by Callables
+    private final Queue<Future<?>> futures = new LinkedList<Future<?>>();
+    private final Object downloadPauseLock = new Object();
+    // global flag to signal when downloads should resume on the server
+    private volatile boolean downloadResume = false;
+
+    private int port = -1;
+
+    public int getPort() {
+        if (port == -1) {
+            throw new IllegalStateException("Cannot retrieve port before calling play()");
+        }
+        return port;
+    }
+
+    /**
+     * Returns a URL for connecting to this server.
+     *
+     * @param path the request path, such as "/".
+     */
+    public URL getUrl(String path) throws MalformedURLException {
+        return new URL("http://localhost:" + getPort() + path);
+    }
+
+    /**
+     * Sets the number of bytes of the POST body to keep in memory to the given
+     * limit.
+     */
+    public void setBodyLimit(int maxBodyLength) {
+        this.bodyLimit = maxBodyLength;
+    }
+
+    public void enqueue(MockResponse response) {
+        responseQueue.add(response);
+    }
+
+    /**
+     * Awaits the next HTTP request, removes it, and returns it. Callers should
+     * use this to verify the request sent was as intended.
+     */
+    public RecordedRequest takeRequest() throws InterruptedException {
+        return requestQueue.take();
+    }
+
+    public RecordedRequest takeRequestWithTimeout(long timeoutMillis) throws InterruptedException {
+        return requestQueue.poll(timeoutMillis, TimeUnit.MILLISECONDS);
+    }
+
+    public List<RecordedRequest> drainRequests() {
+        List<RecordedRequest> requests = new ArrayList<RecordedRequest>();
+        requestQueue.drainTo(requests);
+        return requests;
+    }
+
+    /**
+     * Starts the server, serves all enqueued requests, and shuts the server
+     * down using the default (server-assigned) port.
+     */
+    public void play() throws IOException {
+        play(0);
+    }
+
+    /**
+     * Starts the server, serves all enqueued requests, and shuts the server
+     * down.
+     *
+     * @param port The port number to use to listen to connections on; pass in 0 to have the
+     * server automatically assign a free port
+     */
+    public void play(int portNumber) throws IOException {
+        final ServerSocket ss = new ServerSocket(portNumber);
+        ss.setReuseAddress(true);
+        port = ss.getLocalPort();
+        submitCallable(new Callable<Void>() {
+            public Void call() throws Exception {
+                int count = 0;
+                while (true) {
+                    if (count > 0 && responseQueue.isEmpty()) {
+                        ss.close();
+                        executor.shutdown();
+                        return null;
+                    }
+
+                    serveConnection(ss.accept());
+                    count++;
+                }
+            }
+        });
+    }
+
+    private void serveConnection(final Socket s) {
+        submitCallable(new Callable<Void>() {
+            public Void call() throws Exception {
+                InputStream in = new BufferedInputStream(s.getInputStream());
+                OutputStream out = new BufferedOutputStream(s.getOutputStream());
+
+                int sequenceNumber = 0;
+                while (true) {
+                    RecordedRequest request = readRequest(in, sequenceNumber);
+                    if (request == null) {
+                        if (sequenceNumber == 0) {
+                            throw new IllegalStateException("Connection without any request!");
+                        } else {
+                            break;
+                        }
+                    }
+                    requestQueue.add(request);
+                    MockResponse response = computeResponse(request);
+                    writeResponse(out, response);
+                    if (response.shouldCloseConnectionAfter()) {
+                        break;
+                    }
+                    sequenceNumber++;
+                }
+
+                in.close();
+                out.close();
+                return null;
+            }
+        });
+    }
+
+    private void submitCallable(Callable<?> callable) {
+        Future<?> future = executor.submit(callable);
+        futures.add(future);
+    }
+
+    /**
+     * Check for and raise any exceptions that have been thrown by child threads.  Will not block on
+     * children still running.
+     * @throws ExecutionException for the first child thread that threw an exception
+     */
+    public void checkForExceptions() throws ExecutionException, InterruptedException {
+        final int originalSize = futures.size();
+        for (int i = 0; i < originalSize; i++) {
+            Future<?> future = futures.remove();
+            try {
+                future.get(0, TimeUnit.SECONDS);
+            } catch (TimeoutException e) {
+                futures.add(future); // still running
+            }
+        }
+    }
+
+    /**
+     * @param sequenceNumber the index of this request on this connection.
+     */
+    private RecordedRequest readRequest(InputStream in, int sequenceNumber) throws IOException {
+        String request = readAsciiUntilCrlf(in);
+        if (request.equals("")) {
+            return null; // end of data; no more requests
+        }
+
+        List<String> headers = new ArrayList<String>();
+        int contentLength = -1;
+        boolean chunked = false;
+        String header;
+        while (!(header = readAsciiUntilCrlf(in)).equals("")) {
+            headers.add(header);
+            String lowercaseHeader = header.toLowerCase();
+            if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) {
+                contentLength = Integer.parseInt(header.substring(15).trim());
+            }
+            if (lowercaseHeader.startsWith("transfer-encoding:") &&
+                    lowercaseHeader.substring(18).trim().equals("chunked")) {
+                chunked = true;
+            }
+        }
+
+        boolean hasBody = false;
+        TruncatingOutputStream requestBody = new TruncatingOutputStream();
+        List<Integer> chunkSizes = new ArrayList<Integer>();
+        if (contentLength != -1) {
+            hasBody = true;
+            transfer(contentLength, in, requestBody);
+        } else if (chunked) {
+            hasBody = true;
+            while (true) {
+                int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16);
+                if (chunkSize == 0) {
+                    readEmptyLine(in);
+                    break;
+                }
+                chunkSizes.add(chunkSize);
+                transfer(chunkSize, in, requestBody);
+                readEmptyLine(in);
+            }
+        }
+
+        if (request.startsWith("GET ")) {
+            if (hasBody) {
+                throw new IllegalArgumentException("GET requests should not have a body!");
+            }
+        } else if (request.startsWith("POST ")) {
+            if (!hasBody) {
+                throw new IllegalArgumentException("POST requests must have a body!");
+            }
+        } else {
+            throw new UnsupportedOperationException("Unexpected method: " + request);
+        }
+
+        return new RecordedRequest(request, headers, chunkSizes,
+                requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber);
+    }
+
+    /**
+     * Returns a response to satisfy {@code request}.
+     */
+    private MockResponse computeResponse(RecordedRequest request) throws InterruptedException {
+        if (responseQueue.isEmpty()) {
+            throw new IllegalStateException("Unexpected request: " + request);
+        }
+        return responseQueue.take();
+    }
+
+    private void writeResponse(OutputStream out, MockResponse response) throws IOException {
+        out.write((response.getStatus() + "\r\n").getBytes(ASCII));
+        boolean doCloseConnectionAfterHeader = (response.getCloseConnectionAfterHeader() != null);
+
+        // Send headers
+        String closeConnectionAfterHeader = response.getCloseConnectionAfterHeader();
+        for (String header : response.getHeaders()) {
+            out.write((header + "\r\n").getBytes(ASCII));
+
+            if (doCloseConnectionAfterHeader && header.startsWith(closeConnectionAfterHeader)) {
+                Log.i(LOG_TAG, "Closing connection after header" + header);
+                break;
+            }
+        }
+
+        // Send actual body data
+        if (!doCloseConnectionAfterHeader) {
+            out.write(("\r\n").getBytes(ASCII));
+
+            InputStream body = response.getBody();
+            final int READ_BLOCK_SIZE = 10000;  // process blocks this size
+            byte[] currentBlock = new byte[READ_BLOCK_SIZE];
+            int currentBlockSize = 0;
+            int writtenSoFar = 0;
+
+            boolean shouldPause = response.getShouldPause();
+            boolean shouldClose = response.getShouldClose();
+            int pause = response.getPauseConnectionAfterXBytes();
+            int close = response.getCloseConnectionAfterXBytes();
+
+            // Don't bother pausing if it's set to pause -after- the connection should be dropped
+            if (shouldPause && shouldClose && (pause > close)) {
+                shouldPause = false;
+            }
+
+            // Process each block we read in...
+            while ((currentBlockSize = body.read(currentBlock)) != -1) {
+                int startIndex = 0;
+                int writeLength = currentBlockSize;
+
+                // handle the case of pausing
+                if (shouldPause && (writtenSoFar + currentBlockSize >= pause)) {
+                    writeLength = pause - writtenSoFar;
+                    out.write(currentBlock, 0, writeLength);
+                    out.flush();
+                    writtenSoFar += writeLength;
+
+                    // now pause...
+                    try {
+                        Log.i(LOG_TAG, "Pausing connection after " + pause + " bytes");
+                        // Wait until someone tells us to resume sending...
+                        synchronized(downloadPauseLock) {
+                            while (!downloadResume) {
+                                downloadPauseLock.wait();
+                            }
+                            // reset resume back to false
+                            downloadResume = false;
+                        }
+                    } catch (InterruptedException e) {
+                        Log.e(LOG_TAG, "Server was interrupted during pause in download.");
+                    }
+
+                    startIndex = writeLength;
+                    writeLength = currentBlockSize - writeLength;
+                }
+
+                // handle the case of closing the connection
+                if (shouldClose && (writtenSoFar + writeLength > close)) {
+                    writeLength = close - writtenSoFar;
+                    out.write(currentBlock, startIndex, writeLength);
+                    writtenSoFar += writeLength;
+                    Log.i(LOG_TAG, "Closing connection after " + close + " bytes");
+                    break;
+                }
+                out.write(currentBlock, startIndex, writeLength);
+                writtenSoFar += writeLength;
+            }
+        }
+        out.flush();
+    }
+
+    /**
+     * Transfer bytes from {@code in} to {@code out} until either {@code length}
+     * bytes have been transferred or {@code in} is exhausted.
+     */
+    private void transfer(int length, InputStream in, OutputStream out) throws IOException {
+        byte[] buffer = new byte[1024];
+        while (length > 0) {
+            int count = in.read(buffer, 0, Math.min(buffer.length, length));
+            if (count == -1) {
+                return;
+            }
+            out.write(buffer, 0, count);
+            length -= count;
+        }
+    }
+
+    /**
+     * Returns the text from {@code in} until the next "\r\n", or null if
+     * {@code in} is exhausted.
+     */
+    private String readAsciiUntilCrlf(InputStream in) throws IOException {
+        StringBuilder builder = new StringBuilder();
+        while (true) {
+            int c = in.read();
+            if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') {
+                builder.deleteCharAt(builder.length() - 1);
+                return builder.toString();
+            } else if (c == -1) {
+                return builder.toString();
+            } else {
+                builder.append((char) c);
+            }
+        }
+    }
+
+    private void readEmptyLine(InputStream in) throws IOException {
+        String line = readAsciiUntilCrlf(in);
+        if (!line.equals("")) {
+            throw new IllegalStateException("Expected empty but was: " + line);
+        }
+    }
+
+    /**
+     * An output stream that drops data after bodyLimit bytes.
+     */
+    private class TruncatingOutputStream extends ByteArrayOutputStream {
+        private int numBytesReceived = 0;
+        @Override public void write(byte[] buffer, int offset, int len) {
+            numBytesReceived += len;
+            super.write(buffer, offset, Math.min(len, bodyLimit - count));
+        }
+        @Override public void write(int oneByte) {
+            numBytesReceived++;
+            if (count < bodyLimit) {
+                super.write(oneByte);
+            }
+        }
+    }
+
+    /**
+     * Trigger the server to resume sending the download
+     */
+    public void doResumeDownload() {
+        synchronized (downloadPauseLock) {
+            downloadResume = true;
+            downloadPauseLock.notifyAll();
+        }
+    }
+}
diff --git a/core/tests/utillib/src/coretestutils/http/RecordedRequest.java b/core/tests/utillib/src/coretestutils/http/RecordedRequest.java
new file mode 100644
index 0000000..293ff80
--- /dev/null
+++ b/core/tests/utillib/src/coretestutils/http/RecordedRequest.java
@@ -0,0 +1,93 @@
+/*
+ * 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 coretestutils.http;
+
+import java.util.List;
+
+/**
+ * An HTTP request that came into the mock web server.
+ */
+public final class RecordedRequest {
+    private final String requestLine;
+    private final List<String> headers;
+    private final List<Integer> chunkSizes;
+    private final int bodySize;
+    private final byte[] body;
+    private final int sequenceNumber;
+
+    RecordedRequest(String requestLine, List<String> headers, List<Integer> chunkSizes,
+            int bodySize, byte[] body, int sequenceNumber) {
+        this.requestLine = requestLine;
+        this.headers = headers;
+        this.chunkSizes = chunkSizes;
+        this.bodySize = bodySize;
+        this.body = body;
+        this.sequenceNumber = sequenceNumber;
+    }
+
+    public String getRequestLine() {
+        return requestLine;
+    }
+
+    public List<String> getHeaders() {
+        return headers;
+    }
+
+    /**
+     * Returns the sizes of the chunks of this request's body, or an empty list
+     * if the request's body was empty or unchunked.
+     */
+    public List<Integer> getChunkSizes() {
+        return chunkSizes;
+    }
+
+    /**
+     * Returns the total size of the body of this POST request (before
+     * truncation).
+     */
+    public int getBodySize() {
+        return bodySize;
+    }
+
+    /**
+     * Returns the body of this POST request. This may be truncated.
+     */
+    public byte[] getBody() {
+        return body;
+    }
+
+    /**
+     * Returns the index of this request on its HTTP connection. Since a single
+     * HTTP connection may serve multiple requests, each request is assigned its
+     * own sequence number.
+     */
+    public int getSequenceNumber() {
+        return sequenceNumber;
+    }
+
+    @Override public String toString() {
+        return requestLine;
+    }
+
+    public String getMethod() {
+        return getRequestLine().split(" ")[0];
+    }
+
+    public String getPath() {
+        return getRequestLine().split(" ")[1];
+    }
+}
diff --git a/docs/html/guide/topics/ui/dialogs.jd b/docs/html/guide/topics/ui/dialogs.jd
index 74b544b..d047b2d 100644
--- a/docs/html/guide/topics/ui/dialogs.jd
+++ b/docs/html/guide/topics/ui/dialogs.jd
@@ -472,18 +472,25 @@
             progressDialog = new ProgressDialog(NotificationTest.this);
             progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
             progressDialog.setMessage("Loading...");
-            progressThread = new ProgressThread(handler);
-            progressThread.start();
             return progressDialog;
         default:
             return null;
         }
     }
 
+    @Override
+    protected void onPrepareDialog(int id, Dialog dialog) {
+        switch(id) {
+        case PROGRESS_DIALOG:
+            progressDialog.setProgress(0);
+            progressThread = new ProgressThread(handler);
+            progressThread.start();
+    }
+
     // Define the Handler that receives messages from the thread and update the progress
     final Handler handler = new Handler() {
         public void handleMessage(Message msg) {
-            int total = msg.getData().getInt("total");
+            int total = msg.arg1;
             progressDialog.setProgress(total);
             if (total >= 100){
                 dismissDialog(PROGRESS_DIALOG);
@@ -514,9 +521,7 @@
                     Log.e("ERROR", "Thread Interrupted");
                 }
                 Message msg = mHandler.obtainMessage();
-                Bundle b = new Bundle();
-                b.putInt("total", total);
-                msg.setData(b);
+                msg.arg1 = total;
                 mHandler.sendMessage(msg);
                 total++;
             }
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index d9ee3ec..9a19056 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -446,28 +446,30 @@
         Rect srcR = new Rect(x, y, x + width, y + height);
         RectF dstR = new RectF(0, 0, width, height);
 
+        final Config newConfig = source.getConfig() == Config.ARGB_8888 ?
+                Config.ARGB_8888 : Config.RGB_565;
+
         if (m == null || m.isIdentity()) {
-            bitmap = createBitmap(neww, newh,
-                    source.hasAlpha() ? Config.ARGB_8888 : Config.RGB_565);
+            bitmap = createBitmap(neww, newh, newConfig, source.hasAlpha());
             paint = null;   // not needed
         } else {
-            /*  the dst should have alpha if the src does, or if our matrix
-                doesn't preserve rectness
-            */
-            boolean hasAlpha = source.hasAlpha() || !m.rectStaysRect();
+            final boolean transformed = !m.rectStaysRect();
+
             RectF deviceR = new RectF();
             m.mapRect(deviceR, dstR);
+
             neww = Math.round(deviceR.width());
             newh = Math.round(deviceR.height());
-            bitmap = createBitmap(neww, newh, hasAlpha ? Config.ARGB_8888 : Config.RGB_565);
-            if (hasAlpha) {
-                bitmap.eraseColor(0);
-            }
+
+            bitmap = createBitmap(neww, newh, transformed ? Config.ARGB_8888 : newConfig,
+                    transformed || source.hasAlpha());
+
             canvas.translate(-deviceR.left, -deviceR.top);
             canvas.concat(m);
+
             paint = new Paint();
             paint.setFilterBitmap(filter);
-            if (!m.rectStaysRect()) {
+            if (transformed) {
                 paint.setAntiAlias(true);
             }
         }
@@ -492,8 +494,30 @@
      * @throws IllegalArgumentException if the width or height are <= 0
      */
     public static Bitmap createBitmap(int width, int height, Config config) {
+        return createBitmap(width, height, config, true);
+    }
+
+    /**
+     * Returns a mutable bitmap with the specified width and height.  Its
+     * initial density is as per {@link #getDensity}.
+     *
+     * @param width    The width of the bitmap
+     * @param height   The height of the bitmap
+     * @param config   The bitmap config to create.
+     * @param hasAlpha If the bitmap is ARGB_8888 this flag can be used to mark the
+     *                 bitmap as opaque. Doing so will clear the bitmap in black
+     *                 instead of transparent.  
+     * 
+     * @throws IllegalArgumentException if the width or height are <= 0
+     */
+    private static Bitmap createBitmap(int width, int height, Config config, boolean hasAlpha) {
         Bitmap bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true);
-        bm.eraseColor(0);    // start with black/transparent pixels
+        if (config == Config.ARGB_8888 && !hasAlpha) {
+            bm.eraseColor(0xff000000);
+            nativeSetHasAlpha(bm.mNativeBitmap, hasAlpha);
+        } else {
+            bm.eraseColor(0);
+        }
         return bm;
     }
 
@@ -1094,7 +1118,7 @@
     private static native void nativePrepareToDraw(int nativeBitmap);
     private static native void nativeSetHasAlpha(int nBitmap, boolean hasAlpha);
     private static native boolean nativeSameAs(int nb0, int nb1);
-
+    
     /* package */ final int ni() {
         return mNativeBitmap;
     }
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index 5dbbd70..dc21a72 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -83,7 +83,7 @@
         /**
          * The pixel density to use for the bitmap.  This will always result
          * in the returned bitmap having a density set for it (see
-         * {@link Bitmap#setDensity(int) Bitmap.setDensity(int))}.  In addition,
+         * {@link Bitmap#setDensity(int) Bitmap.setDensity(int)}).  In addition,
          * if {@link #inScaled} is set (which it is by default} and this
          * density does not match {@link #inTargetDensity}, then the bitmap
          * will be scaled to the target density before being returned.
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 47ab355..f70bca7 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -133,6 +133,7 @@
 
     glViewport(0, 0, mWidth, mHeight);
 
+    glDisable(GL_DITHER);
     glDisable(GL_SCISSOR_TEST);
 
     glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index e8afe37..70e06a1 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -111,7 +111,6 @@
     for (uint32_t i = 0; i < mCache.size(); i++) {
         if (mCache.getKeyAt(i).path == path) {
             mCache.removeAt(i);
-            return;
         }
     }
 }
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 7d8ef58..8ca6237 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -1175,9 +1175,12 @@
             mGenreCache = new HashMap<String, Uri>();
             mGenresUri = Genres.getContentUri(volumeName);
             mPlaylistsUri = Playlists.getContentUri(volumeName);
-            // assuming external storage is FAT (case insensitive), except on the simulator.
-            if ( Process.supportsProcesses()) {
-                mCaseInsensitivePaths = true;
+
+            mCaseInsensitivePaths = !mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_caseSensitiveExternalStorage);
+            if (!Process.supportsProcesses()) {
+                // Simulator uses host file system, so it should be case sensitive.
+                mCaseInsensitivePaths = false;
             }
         }
     }
diff --git a/opengl/tests/angeles/app-linux.cpp b/opengl/tests/angeles/app-linux.cpp
index 06fa0c2..9f80ed4 100644
--- a/opengl/tests/angeles/app-linux.cpp
+++ b/opengl/tests/angeles/app-linux.cpp
@@ -190,24 +190,33 @@
     }
 
     appInit();
+
+    struct timeval timeTemp;
+    int frameCount = 0;
+    gettimeofday(&timeTemp, NULL);
+    double totalTime = timeTemp.tv_usec/1000000.0 + timeTemp.tv_sec;
     
     while (gAppAlive)
     {
         struct timeval timeNow;
 
-        if (gAppAlive)
-        {
-            gettimeofday(&timeNow, NULL);
-            appRender(timeNow.tv_sec * 1000 + timeNow.tv_usec / 1000,
-                      sWindowWidth, sWindowHeight);
-            checkGLErrors();
-            eglSwapBuffers(sEglDisplay, sEglSurface);
-            checkEGLErrors();
-        }
+        gettimeofday(&timeNow, NULL);
+        appRender(timeNow.tv_sec * 1000 + timeNow.tv_usec / 1000,
+                sWindowWidth, sWindowHeight);
+        checkGLErrors();
+        eglSwapBuffers(sEglDisplay, sEglSurface);
+        checkEGLErrors();
+        frameCount++;
     }
 
+    gettimeofday(&timeTemp, NULL);
+
     appDeinit();
     deinitGraphics();
 
+    totalTime = (timeTemp.tv_usec/1000000.0 + timeTemp.tv_sec) - totalTime;
+    printf("totalTime=%f s, frameCount=%d, %.2f fps\n",
+            totalTime, frameCount, frameCount/totalTime);
+
     return EXIT_SUCCESS;
 }
diff --git a/services/java/com/android/server/sip/SipService.java b/services/java/com/android/server/sip/SipService.java
index 563ce58..eee97c3 100644
--- a/services/java/com/android/server/sip/SipService.java
+++ b/services/java/com/android/server/sip/SipService.java
@@ -490,7 +490,7 @@
 
     private class KeepAliveProcess implements Runnable {
         private static final String TAG = "\\KEEPALIVE/";
-        private static final int INTERVAL = 15;
+        private static final int INTERVAL = 10;
         private SipSessionGroup.SipSessionImpl mSession;
 
         public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) {
diff --git a/voip/jni/rtp/AudioGroup.cpp b/voip/jni/rtp/AudioGroup.cpp
index bb45a9a..3433dcf 100644
--- a/voip/jni/rtp/AudioGroup.cpp
+++ b/voip/jni/rtp/AudioGroup.cpp
@@ -588,7 +588,7 @@
     // Give device socket a reasonable timeout and buffer size.
     timeval tv;
     tv.tv_sec = 0;
-    tv.tv_usec = 1000 * sampleCount / sampleRate * 1000;
+    tv.tv_usec = 1000 * sampleCount / sampleRate * 500;
     if (setsockopt(pair[0], SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) ||
         setsockopt(pair[0], SOL_SOCKET, SO_RCVBUF, &output, sizeof(output)) ||
         setsockopt(pair[1], SOL_SOCKET, SO_SNDBUF, &output, sizeof(output))) {
@@ -793,7 +793,7 @@
 
             status_t status = mRecord.obtainBuffer(&buffer, 1);
             if (status == NO_ERROR) {
-                int count = (buffer.frameCount < toRead) ?
+                int count = ((int)buffer.frameCount < toRead) ?
                         buffer.frameCount : toRead;
                 memcpy(&input[mSampleCount - toRead], buffer.i8, count * 2);
                 toRead -= count;