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