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