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