Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.app;
     18 
     19 import android.app.DownloadManager.Query;
     20 import android.app.DownloadManager.Request;
     21 import android.content.BroadcastReceiver;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.IntentFilter;
     25 import android.database.Cursor;
     26 import android.net.ConnectivityManager;
     27 import android.net.NetworkInfo;
     28 import android.net.Uri;
     29 import android.net.wifi.WifiManager;
     30 import android.os.Bundle;
     31 import android.os.Environment;
     32 import android.os.ParcelFileDescriptor;
     33 import android.os.SystemClock;
     34 import android.os.ParcelFileDescriptor.AutoCloseInputStream;
     35 import android.provider.Settings;
     36 import android.test.InstrumentationTestCase;
     37 import android.util.Log;
     38 
     39 import java.io.DataInputStream;
     40 import java.io.DataOutputStream;
     41 import java.io.File;
     42 import java.io.FileInputStream;
     43 import java.io.FileOutputStream;
     44 import java.io.IOException;
     45 import java.net.URL;
     46 import java.util.concurrent.TimeoutException;
     47 import java.util.Collections;
     48 import java.util.HashSet;
     49 import java.util.Iterator;
     50 import java.util.List;
     51 import java.util.Random;
     52 import java.util.Set;
     53 import java.util.Vector;
     54 
     55 import junit.framework.AssertionFailedError;
     56 
     57 import coretestutils.http.MockResponse;
     58 import coretestutils.http.MockWebServer;
     59 
     60 /**
     61  * Base class for Instrumented tests for the Download Manager.
     62  */
     63 public class DownloadManagerBaseTest extends InstrumentationTestCase {
     64 
     65     protected DownloadManager mDownloadManager = null;
     66     protected MockWebServer mServer = null;
     67     protected String mFileType = "text/plain";
     68     protected Context mContext = null;
     69     protected MultipleDownloadsCompletedReceiver mReceiver = null;
     70     protected static final int DEFAULT_FILE_SIZE = 130 * 1024;  // 130kb
     71     protected static final int FILE_BLOCK_READ_SIZE = 1024 * 1024;
     72 
     73     protected static final String LOG_TAG = "android.net.DownloadManagerBaseTest";
     74     protected static final int HTTP_OK = 200;
     75     protected static final int HTTP_REDIRECT = 307;
     76     protected static final int HTTP_PARTIAL_CONTENT = 206;
     77     protected static final int HTTP_NOT_FOUND = 404;
     78     protected static final int HTTP_SERVICE_UNAVAILABLE = 503;
     79     protected String DEFAULT_FILENAME = "somefile.txt";
     80 
     81     protected static final int DEFAULT_MAX_WAIT_TIME = 2 * 60 * 1000;  // 2 minutes
     82     protected static final int DEFAULT_WAIT_POLL_TIME = 5 * 1000;  // 5 seconds
     83 
     84     protected static final int WAIT_FOR_DOWNLOAD_POLL_TIME = 1 * 1000;  // 1 second
     85     protected static final int MAX_WAIT_FOR_DOWNLOAD_TIME = 5 * 60 * 1000; // 5 minutes
     86     protected static final int MAX_WAIT_FOR_LARGE_DOWNLOAD_TIME = 15 * 60 * 1000; // 15 minutes
     87 
     88     // Just a few popular file types used to return from a download
     89     protected enum DownloadFileType {
     90         PLAINTEXT,
     91         APK,
     92         GIF,
     93         GARBAGE,
     94         UNRECOGNIZED,
     95         ZIP
     96     }
     97 
     98     protected enum DataType {
     99         TEXT,
    100         BINARY
    101     }
    102 
    103     public static class LoggingRng extends Random {
    104 
    105         /**
    106          * Constructor
    107          *
    108          * Creates RNG with self-generated seed value.
    109          */
    110         public LoggingRng() {
    111             this(SystemClock.uptimeMillis());
    112         }
    113 
    114         /**
    115          * Constructor
    116          *
    117          * Creats RNG with given initial seed value
    118 
    119          * @param seed The initial seed value
    120          */
    121         public LoggingRng(long seed) {
    122             super(seed);
    123             Log.i(LOG_TAG, "Seeding RNG with value: " + seed);
    124         }
    125     }
    126 
    127     public static class MultipleDownloadsCompletedReceiver extends BroadcastReceiver {
    128         private volatile int mNumDownloadsCompleted = 0;
    129         private Set<Long> downloadIds = Collections.synchronizedSet(new HashSet<Long>());
    130 
    131         /**
    132          * {@inheritDoc}
    133          */
    134         @Override
    135         public void onReceive(Context context, Intent intent) {
    136             if (intent.getAction().equalsIgnoreCase(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
    137                 synchronized(this) {
    138                     long id = intent.getExtras().getLong(DownloadManager.EXTRA_DOWNLOAD_ID);
    139                     Log.i(LOG_TAG, "Received Notification for download: " + id);
    140                     if (!downloadIds.contains(id)) {
    141                         ++mNumDownloadsCompleted;
    142                         Log.i(LOG_TAG, "MultipleDownloadsCompletedReceiver got intent: " +
    143                                 intent.getAction() + " --> total count: " + mNumDownloadsCompleted);
    144                         downloadIds.add(id);
    145 
    146                         DownloadManager dm = (DownloadManager)context.getSystemService(
    147                                 Context.DOWNLOAD_SERVICE);
    148 
    149                         Cursor cursor = dm.query(new Query().setFilterById(id));
    150                         try {
    151                             if (cursor.moveToFirst()) {
    152                                 int status = cursor.getInt(cursor.getColumnIndex(
    153                                         DownloadManager.COLUMN_STATUS));
    154                                 Log.i(LOG_TAG, "Download status is: " + status);
    155                             } else {
    156                                 fail("No status found for completed download!");
    157                             }
    158                         } finally {
    159                             cursor.close();
    160                         }
    161                     } else {
    162                         Log.i(LOG_TAG, "Notification for id: " + id + " has already been made.");
    163                     }
    164                 }
    165             }
    166         }
    167 
    168         /**
    169          * Gets the number of times the {@link #onReceive} callback has been called for the
    170          * {@link DownloadManager.ACTION_DOWNLOAD_COMPLETED} action, indicating the number of
    171          * downloads completed thus far.
    172          *
    173          * @return the number of downloads completed so far.
    174          */
    175         public int numDownloadsCompleted() {
    176             return mNumDownloadsCompleted;
    177         }
    178 
    179         /**
    180          * Gets the list of download IDs.
    181          * @return A Set<Long> with the ids of the completed downloads.
    182          */
    183         public Set<Long> getDownloadIds() {
    184             synchronized(this) {
    185                 Set<Long> returnIds = new HashSet<Long>(downloadIds);
    186                 return returnIds;
    187             }
    188         }
    189 
    190     }
    191 
    192     public static class WiFiChangedReceiver extends BroadcastReceiver {
    193         private Context mContext = null;
    194 
    195         /**
    196          * Constructor
    197          *
    198          * Sets the current state of WiFi.
    199          *
    200          * @param context The current app {@link Context}.
    201          */
    202         public WiFiChangedReceiver(Context context) {
    203             mContext = context;
    204         }
    205 
    206         /**
    207          * {@inheritDoc}
    208          */
    209         @Override
    210         public void onReceive(Context context, Intent intent) {
    211             if (intent.getAction().equalsIgnoreCase(ConnectivityManager.CONNECTIVITY_ACTION)) {
    212                 Log.i(LOG_TAG, "ConnectivityManager state change: " + intent.getAction());
    213                 synchronized (this) {
    214                     this.notify();
    215                 }
    216             }
    217         }
    218 
    219         /**
    220          * Gets the current state of WiFi.
    221          *
    222          * @return Returns true if WiFi is on, false otherwise.
    223          */
    224         public boolean getWiFiIsOn() {
    225             ConnectivityManager connManager = (ConnectivityManager)mContext.getSystemService(
    226                     Context.CONNECTIVITY_SERVICE);
    227             NetworkInfo info = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
    228             Log.i(LOG_TAG, "WiFi Connection state is currently: " + info.isConnected());
    229             return info.isConnected();
    230         }
    231     }
    232 
    233     /**
    234      * {@inheritDoc}
    235      */
    236     @Override
    237     public void setUp() throws Exception {
    238         mContext = getInstrumentation().getContext();
    239         mDownloadManager = (DownloadManager)mContext.getSystemService(Context.DOWNLOAD_SERVICE);
    240         mServer = new MockWebServer();
    241         mReceiver = registerNewMultipleDownloadsReceiver();
    242         // Note: callers overriding this should call mServer.play() with the desired port #
    243     }
    244 
    245     /**
    246      * Helper to enqueue a response from the MockWebServer with no body.
    247      *
    248      * @param status The HTTP status code to return for this response
    249      * @return Returns the mock web server response that was queued (which can be modified)
    250      */
    251     protected MockResponse enqueueResponse(int status) {
    252         return doEnqueueResponse(status);
    253 
    254     }
    255 
    256     /**
    257      * Helper to enqueue a response from the MockWebServer.
    258      *
    259      * @param status The HTTP status code to return for this response
    260      * @param body The body to return in this response
    261      * @return Returns the mock web server response that was queued (which can be modified)
    262      */
    263     protected MockResponse enqueueResponse(int status, byte[] body) {
    264         return doEnqueueResponse(status).setBody(body);
    265 
    266     }
    267 
    268     /**
    269      * Helper to enqueue a response from the MockWebServer.
    270      *
    271      * @param status The HTTP status code to return for this response
    272      * @param bodyFile The body to return in this response
    273      * @return Returns the mock web server response that was queued (which can be modified)
    274      */
    275     protected MockResponse enqueueResponse(int status, File bodyFile) {
    276         return doEnqueueResponse(status).setBody(bodyFile);
    277     }
    278 
    279     /**
    280      * Helper for enqueue'ing a response from the MockWebServer.
    281      *
    282      * @param status The HTTP status code to return for this response
    283      * @return Returns the mock web server response that was queued (which can be modified)
    284      */
    285     protected MockResponse doEnqueueResponse(int status) {
    286         MockResponse response = new MockResponse().setResponseCode(status);
    287         response.addHeader("Content-type", mFileType);
    288         mServer.enqueue(response);
    289         return response;
    290     }
    291 
    292     /**
    293      * Helper to generate a random blob of bytes.
    294      *
    295      * @param size The size of the data to generate
    296      * @param type The type of data to generate: currently, one of {@link DataType.TEXT} or
    297      *         {@link DataType.BINARY}.
    298      * @return The random data that is generated.
    299      */
    300     protected byte[] generateData(int size, DataType type) {
    301         return generateData(size, type, null);
    302     }
    303 
    304     /**
    305      * Helper to generate a random blob of bytes using a given RNG.
    306      *
    307      * @param size The size of the data to generate
    308      * @param type The type of data to generate: currently, one of {@link DataType.TEXT} or
    309      *         {@link DataType.BINARY}.
    310      * @param rng (optional) The RNG to use; pass null to use
    311      * @return The random data that is generated.
    312      */
    313     protected byte[] generateData(int size, DataType type, Random rng) {
    314         int min = Byte.MIN_VALUE;
    315         int max = Byte.MAX_VALUE;
    316 
    317         // Only use chars in the HTTP ASCII printable character range for Text
    318         if (type == DataType.TEXT) {
    319             min = 32;
    320             max = 126;
    321         }
    322         byte[] result = new byte[size];
    323         Log.i(LOG_TAG, "Generating data of size: " + size);
    324 
    325         if (rng == null) {
    326             rng = new LoggingRng();
    327         }
    328 
    329         for (int i = 0; i < size; ++i) {
    330             result[i] = (byte) (min + rng.nextInt(max - min + 1));
    331         }
    332         return result;
    333     }
    334 
    335     /**
    336      * Helper to verify the size of a file.
    337      *
    338      * @param pfd The input file to compare the size of
    339      * @param size The expected size of the file
    340      */
    341     protected void verifyFileSize(ParcelFileDescriptor pfd, long size) {
    342         assertEquals(pfd.getStatSize(), size);
    343     }
    344 
    345     /**
    346      * Helper to verify the contents of a downloaded file versus a byte[].
    347      *
    348      * @param actual The file of whose contents to verify
    349      * @param expected The data we expect to find in the aforementioned file
    350      * @throws IOException if there was a problem reading from the file
    351      */
    352     protected void verifyFileContents(ParcelFileDescriptor actual, byte[] expected)
    353             throws IOException {
    354         AutoCloseInputStream input = new ParcelFileDescriptor.AutoCloseInputStream(actual);
    355         long fileSize = actual.getStatSize();
    356 
    357         assertTrue(fileSize <= Integer.MAX_VALUE);
    358         assertEquals(expected.length, fileSize);
    359 
    360         byte[] actualData = new byte[expected.length];
    361         assertEquals(input.read(actualData), fileSize);
    362         compareByteArrays(actualData, expected);
    363     }
    364 
    365     /**
    366      * Helper to compare 2 byte arrays.
    367      *
    368      * @param actual The array whose data we want to verify
    369      * @param expected The array of data we expect to see
    370      */
    371     protected void compareByteArrays(byte[] actual, byte[] expected) {
    372         assertEquals(actual.length, expected.length);
    373         int length = actual.length;
    374         for (int i = 0; i < length; ++i) {
    375             // assert has a bit of overhead, so only do the assert when the values are not the same
    376             if (actual[i] != expected[i]) {
    377                 fail("Byte arrays are not equal.");
    378             }
    379         }
    380     }
    381 
    382     /**
    383      * Verifies the contents of a downloaded file versus the contents of a File.
    384      *
    385      * @param pfd The file whose data we want to verify
    386      * @param file The file containing the data we expect to see in the aforementioned file
    387      * @throws IOException If there was a problem reading either of the two files
    388      */
    389     protected void verifyFileContents(ParcelFileDescriptor pfd, File file) throws IOException {
    390         byte[] actual = new byte[FILE_BLOCK_READ_SIZE];
    391         byte[] expected = new byte[FILE_BLOCK_READ_SIZE];
    392 
    393         AutoCloseInputStream input = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
    394 
    395         assertEquals(file.length(), pfd.getStatSize());
    396 
    397         DataInputStream inFile = new DataInputStream(new FileInputStream(file));
    398         int actualRead = 0;
    399         int expectedRead = 0;
    400 
    401         while (((actualRead = input.read(actual)) != -1) &&
    402                 ((expectedRead = inFile.read(expected)) != -1)) {
    403             assertEquals(actualRead, expectedRead);
    404             compareByteArrays(actual, expected);
    405         }
    406     }
    407 
    408     /**
    409      * Sets the MIME type of file that will be served from the mock server
    410      *
    411      * @param type The MIME type to return from the server
    412      */
    413     protected void setServerMimeType(DownloadFileType type) {
    414         mFileType = getMimeMapping(type);
    415     }
    416 
    417     /**
    418      * Gets the MIME content string for a given type
    419      *
    420      * @param type The MIME type to return
    421      * @return the String representation of that MIME content type
    422      */
    423     protected String getMimeMapping(DownloadFileType type) {
    424         switch (type) {
    425             case APK:
    426                 return "application/vnd.android.package-archive";
    427             case GIF:
    428                 return "image/gif";
    429             case ZIP:
    430                 return "application/x-zip-compressed";
    431             case GARBAGE:
    432                 return "zip\\pidy/doo/da";
    433             case UNRECOGNIZED:
    434                 return "application/new.undefined.type.of.app";
    435         }
    436         return "text/plain";
    437     }
    438 
    439     /**
    440      * Gets the Uri that should be used to access the mock server
    441      *
    442      * @param filename The name of the file to try to retrieve from the mock server
    443      * @return the Uri to use for access the file on the mock server
    444      */
    445     protected Uri getServerUri(String filename) throws Exception {
    446         URL url = mServer.getUrl("/" + filename);
    447         return Uri.parse(url.toString());
    448     }
    449 
    450    /**
    451     * Gets the Uri that should be used to access the mock server
    452     *
    453     * @param filename The name of the file to try to retrieve from the mock server
    454     * @return the Uri to use for access the file on the mock server
    455     */
    456     protected void logDBColumnData(Cursor cursor, String column) {
    457         int index = cursor.getColumnIndex(column);
    458         Log.i(LOG_TAG, "columnName: " + column);
    459         Log.i(LOG_TAG, "columnValue: " + cursor.getString(index));
    460     }
    461 
    462     /**
    463      * Helper to create and register a new MultipleDownloadCompletedReciever
    464      *
    465      * This is used to track many simultaneous downloads by keeping count of all the downloads
    466      * that have completed.
    467      *
    468      * @return A new receiver that records and can be queried on how many downloads have completed.
    469      */
    470     protected MultipleDownloadsCompletedReceiver registerNewMultipleDownloadsReceiver() {
    471         MultipleDownloadsCompletedReceiver receiver = new MultipleDownloadsCompletedReceiver();
    472         mContext.registerReceiver(receiver, new IntentFilter(
    473                 DownloadManager.ACTION_DOWNLOAD_COMPLETE));
    474         return receiver;
    475     }
    476 
    477     /**
    478      * Helper to verify a standard single-file download from the mock server, and clean up after
    479      * verification
    480      *
    481      * Note that this also calls the Download manager's remove, which cleans up the file from cache.
    482      *
    483      * @param requestId The id of the download to remove
    484      * @param fileData The data to verify the file contains
    485      */
    486     protected void verifyAndCleanupSingleFileDownload(long requestId, byte[] fileData)
    487             throws Exception {
    488         int fileSize = fileData.length;
    489         ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(requestId);
    490         Cursor cursor = mDownloadManager.query(new Query().setFilterById(requestId));
    491 
    492         try {
    493             assertEquals(1, cursor.getCount());
    494             assertTrue(cursor.moveToFirst());
    495 
    496             mServer.checkForExceptions();
    497 
    498             verifyFileSize(pfd, fileSize);
    499             verifyFileContents(pfd, fileData);
    500         } finally {
    501             pfd.close();
    502             cursor.close();
    503             mDownloadManager.remove(requestId);
    504         }
    505     }
    506 
    507     /**
    508      * Enables or disables WiFi.
    509      *
    510      * Note: Needs the following permissions:
    511      *  android.permission.ACCESS_WIFI_STATE
    512      *  android.permission.CHANGE_WIFI_STATE
    513      * @param enable true if it should be enabled, false if it should be disabled
    514      */
    515     protected void setWiFiStateOn(boolean enable) throws Exception {
    516         Log.i(LOG_TAG, "Setting WiFi State to: " + enable);
    517         WifiManager manager = (WifiManager)mContext.getSystemService(Context.WIFI_SERVICE);
    518 
    519         manager.setWifiEnabled(enable);
    520 
    521         String timeoutMessage = "Timed out waiting for Wifi to be "
    522             + (enable ? "enabled!" : "disabled!");
    523 
    524         WiFiChangedReceiver receiver = new WiFiChangedReceiver(mContext);
    525         mContext.registerReceiver(receiver, new IntentFilter(
    526                 ConnectivityManager.CONNECTIVITY_ACTION));
    527 
    528         synchronized (receiver) {
    529             long timeoutTime = SystemClock.elapsedRealtime() + DEFAULT_MAX_WAIT_TIME;
    530             boolean timedOut = false;
    531 
    532             while (receiver.getWiFiIsOn() != enable && !timedOut) {
    533                 try {
    534                     receiver.wait(DEFAULT_WAIT_POLL_TIME);
    535 
    536                     if (SystemClock.elapsedRealtime() > timeoutTime) {
    537                         timedOut = true;
    538                     }
    539                 }
    540                 catch (InterruptedException e) {
    541                     // ignore InterruptedExceptions
    542                 }
    543             }
    544             if (timedOut) {
    545                 fail(timeoutMessage);
    546             }
    547         }
    548         assertEquals(enable, receiver.getWiFiIsOn());
    549     }
    550 
    551     /**
    552      * Helper to enables or disables airplane mode. If successful, it also broadcasts an intent
    553      * indicating that the mode has changed.
    554      *
    555      * Note: Needs the following permission:
    556      *  android.permission.WRITE_SETTINGS
    557      * @param enable true if airplane mode should be ON, false if it should be OFF
    558      */
    559     protected void setAirplaneModeOn(boolean enable) throws Exception {
    560         int state = enable ? 1 : 0;
    561 
    562         // Change the system setting
    563         Settings.System.putInt(mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON,
    564                 state);
    565 
    566         String timeoutMessage = "Timed out waiting for airplane mode to be " +
    567                 (enable ? "enabled!" : "disabled!");
    568 
    569         // wait for airplane mode to change state
    570         int currentWaitTime = 0;
    571         while (Settings.System.getInt(mContext.getContentResolver(),
    572                 Settings.System.AIRPLANE_MODE_ON, -1) != state) {
    573             timeoutWait(currentWaitTime, DEFAULT_WAIT_POLL_TIME, DEFAULT_MAX_WAIT_TIME,
    574                     timeoutMessage);
    575         }
    576 
    577         // Post the intent
    578         Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
    579         intent.putExtra("state", true);
    580         mContext.sendBroadcast(intent);
    581     }
    582 
    583     /**
    584      * Helper to create a large file of random data on the SD card.
    585      *
    586      * @param filename (optional) The name of the file to create on the SD card; pass in null to
    587      *          use a default temp filename.
    588      * @param type The type of file to create
    589      * @param subdirectory If not null, the subdirectory under the SD card where the file should go
    590      * @return The File that was created
    591      * @throws IOException if there was an error while creating the file.
    592      */
    593     protected File createFileOnSD(String filename, long fileSize, DataType type,
    594             String subdirectory) throws IOException {
    595 
    596         // Build up the file path and name
    597         String sdPath = Environment.getExternalStorageDirectory().getPath();
    598         StringBuilder fullPath = new StringBuilder(sdPath);
    599         if (subdirectory != null) {
    600             fullPath.append(File.separatorChar).append(subdirectory);
    601         }
    602 
    603         File file = null;
    604         if (filename == null) {
    605             file = File.createTempFile("DMTEST_", null, new File(fullPath.toString()));
    606         }
    607         else {
    608             fullPath.append(File.separatorChar).append(filename);
    609             file = new File(fullPath.toString());
    610             file.createNewFile();
    611         }
    612 
    613         // Fill the file with random data
    614         DataOutputStream output = new DataOutputStream(new FileOutputStream(file));
    615         final int CHUNK_SIZE = 1000000;  // copy random data in 1000000-char chunks
    616         long remaining = fileSize;
    617         int nextChunkSize = CHUNK_SIZE;
    618         byte[] randomData = null;
    619         Random rng = new LoggingRng();
    620 
    621         try {
    622             while (remaining > 0) {
    623                 if (remaining < CHUNK_SIZE) {
    624                     nextChunkSize = (int)remaining;
    625                     remaining = 0;
    626                 }
    627                 else {
    628                     remaining -= CHUNK_SIZE;
    629                 }
    630 
    631                 randomData = generateData(nextChunkSize, type, rng);
    632                 output.write(randomData);
    633             }
    634         } catch (IOException e) {
    635             Log.e(LOG_TAG, "Error writing to file " + file.getAbsolutePath());
    636             file.delete();
    637             throw e;
    638         } finally {
    639             output.close();
    640         }
    641         return file;
    642     }
    643 
    644     /**
    645      * Helper to wait for a particular download to finish, or else a timeout to occur
    646      *
    647      * Does not wait for a receiver notification of the download.
    648      *
    649      * @param id The download id to query on (wait for)
    650      */
    651     protected void waitForDownloadOrTimeout_skipNotification(long id) throws TimeoutException,
    652             InterruptedException {
    653         waitForDownloadOrTimeout(id, WAIT_FOR_DOWNLOAD_POLL_TIME, MAX_WAIT_FOR_DOWNLOAD_TIME);
    654     }
    655 
    656     /**
    657      * Helper to wait for a particular download to finish, or else a timeout to occur
    658      *
    659      * Also guarantees a notification has been posted for the download.
    660      *
    661      * @param id The download id to query on (wait for)
    662      */
    663     protected void waitForDownloadOrTimeout(long id) throws TimeoutException,
    664             InterruptedException {
    665         waitForDownloadOrTimeout_skipNotification(id);
    666         waitForReceiverNotifications(1);
    667     }
    668 
    669     /**
    670      * Helper to wait for a particular download to finish, or else a timeout to occur
    671      *
    672      * Also guarantees a notification has been posted for the download.
    673      *
    674      * @param id The download id to query on (wait for)
    675      * @param poll The amount of time to wait
    676      * @param timeoutMillis The max time (in ms) to wait for the download(s) to complete
    677      */
    678     protected void waitForDownloadOrTimeout(long id, long poll, long timeoutMillis)
    679             throws TimeoutException, InterruptedException {
    680         doWaitForDownloadsOrTimeout(new Query().setFilterById(id), poll, timeoutMillis);
    681         waitForReceiverNotifications(1);
    682     }
    683 
    684     /**
    685      * Helper to wait for all downloads to finish, or else a specified timeout to occur
    686      *
    687      * Makes no guaranee that notifications have been posted for all downloads.
    688      *
    689      * @param poll The amount of time to wait
    690      * @param timeoutMillis The max time (in ms) to wait for the download(s) to complete
    691      */
    692     protected void waitForDownloadsOrTimeout(long poll, long timeoutMillis) throws TimeoutException,
    693             InterruptedException {
    694         doWaitForDownloadsOrTimeout(new Query(), poll, timeoutMillis);
    695     }
    696 
    697     /**
    698      * Helper to wait for all downloads to finish, or else a timeout to occur, but does not throw
    699      *
    700      * Also guarantees a notification has been posted for the download.
    701      *
    702      * @param id The id of the download to query against
    703      * @param poll The amount of time to wait
    704      * @param timeoutMillis The max time (in ms) to wait for the download(s) to complete
    705      * @return true if download completed successfully (didn't timeout), false otherwise
    706      */
    707     protected boolean waitForDownloadOrTimeoutNoThrow(long id, long poll, long timeoutMillis) {
    708         try {
    709             doWaitForDownloadsOrTimeout(new Query().setFilterById(id), poll, timeoutMillis);
    710             waitForReceiverNotifications(1);
    711         } catch (TimeoutException e) {
    712             return false;
    713         }
    714         return true;
    715     }
    716 
    717     /**
    718      * Helper function to synchronously wait, or timeout if the maximum threshold has been exceeded.
    719      *
    720      * @param currentTotalWaitTime The total time waited so far
    721      * @param poll The amount of time to wait
    722      * @param maxTimeoutMillis The total wait time threshold; if we've waited more than this long,
    723      *          we timeout and fail
    724      * @param timedOutMessage The message to display in the failure message if we timeout
    725      * @return The new total amount of time we've waited so far
    726      * @throws TimeoutException if timed out waiting for SD card to mount
    727      */
    728     protected int timeoutWait(int currentTotalWaitTime, long poll, long maxTimeoutMillis,
    729             String timedOutMessage) throws TimeoutException {
    730         long now = SystemClock.elapsedRealtime();
    731         long end = now + poll;
    732 
    733         // if we get InterruptedException's, ignore them and just keep sleeping
    734         while (now < end) {
    735             try {
    736                 Thread.sleep(end - now);
    737             } catch (InterruptedException e) {
    738                 // ignore interrupted exceptions
    739             }
    740             now = SystemClock.elapsedRealtime();
    741         }
    742 
    743         currentTotalWaitTime += poll;
    744         if (currentTotalWaitTime > maxTimeoutMillis) {
    745             throw new TimeoutException(timedOutMessage);
    746         }
    747         return currentTotalWaitTime;
    748     }
    749 
    750     /**
    751      * Helper to wait for all downloads to finish, or else a timeout to occur
    752      *
    753      * @param query The query to pass to the download manager
    754      * @param poll The poll time to wait between checks
    755      * @param timeoutMillis The max amount of time (in ms) to wait for the download(s) to complete
    756      */
    757     protected void doWaitForDownloadsOrTimeout(Query query, long poll, long timeoutMillis)
    758             throws TimeoutException {
    759         int currentWaitTime = 0;
    760         while (true) {
    761             query.setFilterByStatus(DownloadManager.STATUS_PENDING | DownloadManager.STATUS_PAUSED
    762                     | DownloadManager.STATUS_RUNNING);
    763             Cursor cursor = mDownloadManager.query(query);
    764 
    765             try {
    766                 if (cursor.getCount() == 0) {
    767                     Log.i(LOG_TAG, "All downloads should be done...");
    768                     break;
    769                 }
    770                 currentWaitTime = timeoutWait(currentWaitTime, poll, timeoutMillis,
    771                         "Timed out waiting for all downloads to finish");
    772             } finally {
    773                 cursor.close();
    774             }
    775         }
    776     }
    777 
    778     /**
    779      * Synchronously waits for external store to be mounted (eg: SD Card).
    780      *
    781      * @throws InterruptedException if interrupted
    782      * @throws Exception if timed out waiting for SD card to mount
    783      */
    784     protected void waitForExternalStoreMount() throws Exception {
    785         String extStorageState = Environment.getExternalStorageState();
    786         int currentWaitTime = 0;
    787         while (!extStorageState.equals(Environment.MEDIA_MOUNTED)) {
    788             Log.i(LOG_TAG, "Waiting for SD card...");
    789             currentWaitTime = timeoutWait(currentWaitTime, DEFAULT_WAIT_POLL_TIME,
    790                     DEFAULT_MAX_WAIT_TIME, "Timed out waiting for SD Card to be ready!");
    791             extStorageState = Environment.getExternalStorageState();
    792         }
    793     }
    794 
    795     /**
    796      * Synchronously waits for a download to start.
    797      *
    798      * @param dlRequest the download request id used by Download Manager to track the download.
    799      * @throws Exception if timed out while waiting for SD card to mount
    800      */
    801     protected void waitForDownloadToStart(long dlRequest) throws Exception {
    802         Cursor cursor = getCursor(dlRequest);
    803         try {
    804             int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
    805             int value = cursor.getInt(columnIndex);
    806             int currentWaitTime = 0;
    807 
    808             while (value != DownloadManager.STATUS_RUNNING &&
    809                     (value != DownloadManager.STATUS_FAILED) &&
    810                     (value != DownloadManager.STATUS_SUCCESSFUL)) {
    811                 Log.i(LOG_TAG, "Waiting for download to start...");
    812                 currentWaitTime = timeoutWait(currentWaitTime, WAIT_FOR_DOWNLOAD_POLL_TIME,
    813                         MAX_WAIT_FOR_DOWNLOAD_TIME, "Timed out waiting for download to start!");
    814                 cursor.requery();
    815                 assertTrue(cursor.moveToFirst());
    816                 columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
    817                 value = cursor.getInt(columnIndex);
    818             }
    819             assertFalse("Download failed immediately after start",
    820                     value == DownloadManager.STATUS_FAILED);
    821         } finally {
    822             cursor.close();
    823         }
    824     }
    825 
    826     /**
    827      * Convenience function to wait for just 1 notification of a download.
    828      *
    829      * @throws Exception if timed out while waiting
    830      */
    831     protected void waitForReceiverNotification() throws Exception {
    832         waitForReceiverNotifications(1);
    833     }
    834 
    835     /**
    836      * Synchronously waits for our receiver to receive notification for a given number of
    837      * downloads.
    838      *
    839      * @param targetNumber The number of notifications for unique downloads to wait for; pass in
    840      *         -1 to not wait for notification.
    841      * @throws Exception if timed out while waiting
    842      */
    843     protected void waitForReceiverNotifications(int targetNumber) throws TimeoutException {
    844         int count = mReceiver.numDownloadsCompleted();
    845         int currentWaitTime = 0;
    846 
    847         while (count < targetNumber) {
    848             Log.i(LOG_TAG, "Waiting for notification of downloads...");
    849             currentWaitTime = timeoutWait(currentWaitTime, WAIT_FOR_DOWNLOAD_POLL_TIME,
    850                     MAX_WAIT_FOR_DOWNLOAD_TIME, "Timed out waiting for download notifications!"
    851                     + " Received " + count + "notifications.");
    852             count = mReceiver.numDownloadsCompleted();
    853         }
    854     }
    855 
    856     /**
    857      * Synchronously waits for a file to increase in size (such as to monitor that a download is
    858      * progressing).
    859      *
    860      * @param file The file whose size to track.
    861      * @throws Exception if timed out while waiting for the file to grow in size.
    862      */
    863     protected void waitForFileToGrow(File file) throws Exception {
    864         int currentWaitTime = 0;
    865 
    866         // File may not even exist yet, so wait until it does (or we timeout)
    867         while (!file.exists()) {
    868             Log.i(LOG_TAG, "Waiting for file to exist...");
    869             currentWaitTime = timeoutWait(currentWaitTime, WAIT_FOR_DOWNLOAD_POLL_TIME,
    870                     MAX_WAIT_FOR_DOWNLOAD_TIME, "Timed out waiting for file to be created.");
    871         }
    872 
    873         // Get original file size...
    874         long originalSize = file.length();
    875 
    876         while (file.length() <= originalSize) {
    877             Log.i(LOG_TAG, "Waiting for file to be written to...");
    878             currentWaitTime = timeoutWait(currentWaitTime, WAIT_FOR_DOWNLOAD_POLL_TIME,
    879                     MAX_WAIT_FOR_DOWNLOAD_TIME, "Timed out waiting for file to be written to.");
    880         }
    881     }
    882 
    883     /**
    884      * Helper to remove all downloads that are registered with the DL Manager.
    885      *
    886      * Note: This gives us a clean slate b/c it includes downloads that are pending, running,
    887      * paused, or have completed.
    888      */
    889     protected void removeAllCurrentDownloads() {
    890         Log.i(LOG_TAG, "Removing all current registered downloads...");
    891         Cursor cursor = mDownloadManager.query(new Query());
    892         try {
    893             if (cursor.moveToFirst()) {
    894                 do {
    895                     int index = cursor.getColumnIndex(DownloadManager.COLUMN_ID);
    896                     long downloadId = cursor.getLong(index);
    897 
    898                     mDownloadManager.remove(downloadId);
    899                 } while (cursor.moveToNext());
    900             }
    901         } finally {
    902             cursor.close();
    903         }
    904     }
    905 
    906     /**
    907      * Helper to perform a standard enqueue of data to the mock server.
    908      *
    909      * @param body The body to return in the response from the server
    910      */
    911     protected long doStandardEnqueue(byte[] body) throws Exception {
    912         // Prepare the mock server with a standard response
    913         enqueueResponse(HTTP_OK, body);
    914         return doCommonStandardEnqueue();
    915     }
    916 
    917     /**
    918      * Helper to perform a standard enqueue of data to the mock server.
    919      *
    920      * @param body The body to return in the response from the server, contained in the file
    921      */
    922     protected long doStandardEnqueue(File body) throws Exception {
    923         // Prepare the mock server with a standard response
    924         enqueueResponse(HTTP_OK, body);
    925         return doCommonStandardEnqueue();
    926     }
    927 
    928     /**
    929      * Helper to do the additional steps (setting title and Uri of default filename) when
    930      * doing a standard enqueue request to the server.
    931      */
    932     protected long doCommonStandardEnqueue() throws Exception {
    933         Uri uri = getServerUri(DEFAULT_FILENAME);
    934         Request request = new Request(uri);
    935         request.setTitle(DEFAULT_FILENAME);
    936 
    937         long dlRequest = mDownloadManager.enqueue(request);
    938         Log.i(LOG_TAG, "request ID: " + dlRequest);
    939         return dlRequest;
    940     }
    941 
    942     /**
    943      * Helper to verify an int value in a Cursor
    944      *
    945      * @param cursor The cursor containing the query results
    946      * @param columnName The name of the column to query
    947      * @param expected The expected int value
    948      */
    949     protected void verifyInt(Cursor cursor, String columnName, int expected) {
    950         int index = cursor.getColumnIndex(columnName);
    951         int actual = cursor.getInt(index);
    952         assertEquals(expected, actual);
    953     }
    954 
    955     /**
    956      * Helper to verify a String value in a Cursor
    957      *
    958      * @param cursor The cursor containing the query results
    959      * @param columnName The name of the column to query
    960      * @param expected The expected String value
    961      */
    962     protected void verifyString(Cursor cursor, String columnName, String expected) {
    963         int index = cursor.getColumnIndex(columnName);
    964         String actual = cursor.getString(index);
    965         Log.i(LOG_TAG, ": " + actual);
    966         assertEquals(expected, actual);
    967     }
    968 
    969     /**
    970      * Performs a query based on ID and returns a Cursor for the query.
    971      *
    972      * @param id The id of the download in DL Manager; pass -1 to query all downloads
    973      * @return A cursor for the query results
    974      */
    975     protected Cursor getCursor(long id) throws Exception {
    976         Query query = new Query();
    977         if (id != -1) {
    978             query.setFilterById(id);
    979         }
    980 
    981         Cursor cursor = mDownloadManager.query(query);
    982         int currentWaitTime = 0;
    983 
    984         try {
    985             while (!cursor.moveToFirst()) {
    986                 Thread.sleep(DEFAULT_WAIT_POLL_TIME);
    987                 currentWaitTime += DEFAULT_WAIT_POLL_TIME;
    988                 if (currentWaitTime > DEFAULT_MAX_WAIT_TIME) {
    989                     fail("timed out waiting for a non-null query result");
    990                 }
    991                 cursor.requery();
    992             }
    993         } catch (Exception e) {
    994             cursor.close();
    995             throw e;
    996         }
    997         return cursor;
    998     }
    999 
   1000 }