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