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