summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeal Nguyen <tommyn@google.com>2010-08-09 14:08:26 -0700
committerNeal Nguyen <tommyn@google.com>2010-09-08 17:02:53 -0700
commit5f53bca55b2c9e217dee12bff8ce55e168829783 (patch)
tree14f9203ef32283086d3fe6f1ac06d30804c1e2c9
parent3fa7d8af6560de07ef673f73308f7e51de64e4ec (diff)
downloadframeworks_base-5f53bca55b2c9e217dee12bff8ce55e168829783.zip
frameworks_base-5f53bca55b2c9e217dee12bff8ce55e168829783.tar.gz
frameworks_base-5f53bca55b2c9e217dee12bff8ce55e168829783.tar.bz2
Adding Download Manager Integration, stress, and hosts-based tests.
Change-Id: I97008f6cfd95ea9950db0b4e093da02528849b63
-rw-r--r--core/tests/coretests/Android.mk2
-rw-r--r--core/tests/coretests/AndroidManifest.xml4
-rw-r--r--core/tests/coretests/src/android/net/DownloadManagerBaseTest.java888
-rw-r--r--core/tests/coretests/src/android/net/DownloadManagerIntegrationTest.java381
-rw-r--r--core/tests/coretests/src/android/net/DownloadManagerStressTest.java156
-rw-r--r--core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java56
-rw-r--r--core/tests/hosttests/src/android/net/DownloadManagerHostTests.java193
-rw-r--r--core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk29
-rw-r--r--core/tests/hosttests/test-apps/DownloadManagerTestApp/AndroidManifest.xml35
-rw-r--r--core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestApp.java463
-rw-r--r--core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestRunner.java63
-rw-r--r--core/tests/utillib/Android.mk27
-rw-r--r--core/tests/utillib/src/coretestutils/http/MockResponse.java239
-rw-r--r--core/tests/utillib/src/coretestutils/http/MockWebServer.java426
-rw-r--r--core/tests/utillib/src/coretestutils/http/RecordedRequest.java93
15 files changed, 3046 insertions, 9 deletions
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 @@ LOCAL_SRC_FILES := \
$(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 b225c37..1593bb5 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.InputStreamReader;
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 @@ public class PackageManagerHostTestUtils extends Assert {
/**
* 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();
testRunner.run(listener);
return listener;
@@ -138,16 +160,34 @@ public class PackageManagerHostTestUtils extends Assert {
* 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();
}
@@ -531,7 +571,7 @@ public class PackageManagerHostTestUtils extends Assert {
}
// 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];
+ }
+}