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}