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