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