DownloadManagerBaseTest.java revision b14ad8cc8cb0ed774072b077694b21fd0a6f33be
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.Environment; 31import android.os.ParcelFileDescriptor; 32import android.os.ParcelFileDescriptor.AutoCloseInputStream; 33import android.os.SystemClock; 34import android.provider.Settings; 35import android.test.InstrumentationTestCase; 36import android.util.Log; 37 38import com.google.mockwebserver.MockResponse; 39import com.google.mockwebserver.MockWebServer; 40 41import java.io.DataInputStream; 42import java.io.DataOutputStream; 43import java.io.File; 44import java.io.FileInputStream; 45import java.io.FileNotFoundException; 46import java.io.FileOutputStream; 47import java.io.IOException; 48import java.net.URL; 49import java.util.ArrayList; 50import java.util.Collections; 51import java.util.HashSet; 52import java.util.Random; 53import java.util.Set; 54import java.util.concurrent.TimeoutException; 55 56import libcore.io.Streams; 57 58/** 59 * Base class for Instrumented tests for the Download Manager. 60 */ 61public class DownloadManagerBaseTest extends InstrumentationTestCase { 62 private static final String TAG = "DownloadManagerBaseTest"; 63 protected DownloadManager mDownloadManager = null; 64 private MockWebServer mServer = null; 65 protected String mFileType = "text/plain"; 66 protected Context mContext = null; 67 protected MultipleDownloadsCompletedReceiver mReceiver = null; 68 protected static final int DEFAULT_FILE_SIZE = 10 * 1024; // 10kb 69 protected static final int FILE_BLOCK_READ_SIZE = 1024 * 1024; 70 71 protected static final String LOG_TAG = "android.net.DownloadManagerBaseTest"; 72 protected static final int HTTP_OK = 200; 73 protected static final int HTTP_REDIRECT = 307; 74 protected static final int HTTP_PARTIAL_CONTENT = 206; 75 protected static final int HTTP_NOT_FOUND = 404; 76 protected static final int HTTP_SERVICE_UNAVAILABLE = 503; 77 protected String DEFAULT_FILENAME = "somefile.txt"; 78 79 protected static final int DEFAULT_MAX_WAIT_TIME = 2 * 60 * 1000; // 2 minutes 80 protected static final int DEFAULT_WAIT_POLL_TIME = 5 * 1000; // 5 seconds 81 82 protected static final int WAIT_FOR_DOWNLOAD_POLL_TIME = 1 * 1000; // 1 second 83 protected static final int MAX_WAIT_FOR_DOWNLOAD_TIME = 5 * 60 * 1000; // 5 minutes 84 protected static final int MAX_WAIT_FOR_LARGE_DOWNLOAD_TIME = 15 * 60 * 1000; // 15 minutes 85 86 protected static final int DOWNLOAD_TO_SYSTEM_CACHE = 1; 87 protected static final int DOWNLOAD_TO_DOWNLOAD_CACHE_DIR = 2; 88 89 // Just a few popular file types used to return from a download 90 protected enum DownloadFileType { 91 PLAINTEXT, 92 APK, 93 GIF, 94 GARBAGE, 95 UNRECOGNIZED, 96 ZIP 97 } 98 99 protected enum DataType { 100 TEXT, 101 BINARY 102 } 103 104 public static class LoggingRng extends Random { 105 106 /** 107 * Constructor 108 * 109 * Creates RNG with self-generated seed value. 110 */ 111 public LoggingRng() { 112 this(SystemClock.uptimeMillis()); 113 } 114 115 /** 116 * Constructor 117 * 118 * Creats RNG with given initial seed value 119 120 * @param seed The initial seed value 121 */ 122 public LoggingRng(long seed) { 123 super(seed); 124 Log.i(LOG_TAG, "Seeding RNG with value: " + seed); 125 } 126 } 127 128 public static class MultipleDownloadsCompletedReceiver extends BroadcastReceiver { 129 private volatile int mNumDownloadsCompleted = 0; 130 private Set<Long> downloadIds = Collections.synchronizedSet(new HashSet<Long>()); 131 132 /** 133 * {@inheritDoc} 134 */ 135 @Override 136 public void onReceive(Context context, Intent intent) { 137 if (intent.getAction().equalsIgnoreCase(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) { 138 synchronized(this) { 139 long id = intent.getExtras().getLong(DownloadManager.EXTRA_DOWNLOAD_ID); 140 Log.i(LOG_TAG, "Received Notification for download: " + id); 141 if (!downloadIds.contains(id)) { 142 ++mNumDownloadsCompleted; 143 Log.i(LOG_TAG, "MultipleDownloadsCompletedReceiver got intent: " + 144 intent.getAction() + " --> total count: " + mNumDownloadsCompleted); 145 downloadIds.add(id); 146 147 DownloadManager dm = (DownloadManager)context.getSystemService( 148 Context.DOWNLOAD_SERVICE); 149 150 Cursor cursor = dm.query(new Query().setFilterById(id)); 151 try { 152 if (cursor.moveToFirst()) { 153 int status = cursor.getInt(cursor.getColumnIndex( 154 DownloadManager.COLUMN_STATUS)); 155 Log.i(LOG_TAG, "Download status is: " + status); 156 } else { 157 fail("No status found for completed download!"); 158 } 159 } finally { 160 cursor.close(); 161 } 162 } else { 163 Log.i(LOG_TAG, "Notification for id: " + id + " has already been made."); 164 } 165 } 166 } 167 } 168 169 /** 170 * Gets the number of times the {@link #onReceive} callback has been called for the 171 * {@link DownloadManager.ACTION_DOWNLOAD_COMPLETED} action, indicating the number of 172 * downloads completed thus far. 173 * 174 * @return the number of downloads completed so far. 175 */ 176 public int numDownloadsCompleted() { 177 return mNumDownloadsCompleted; 178 } 179 180 /** 181 * Gets the list of download IDs. 182 * @return A Set<Long> with the ids of the completed downloads. 183 */ 184 public Set<Long> getDownloadIds() { 185 synchronized(this) { 186 Set<Long> returnIds = new HashSet<Long>(downloadIds); 187 return returnIds; 188 } 189 } 190 191 } 192 193 public static class WiFiChangedReceiver extends BroadcastReceiver { 194 private Context mContext = null; 195 196 /** 197 * Constructor 198 * 199 * Sets the current state of WiFi. 200 * 201 * @param context The current app {@link Context}. 202 */ 203 public WiFiChangedReceiver(Context context) { 204 mContext = context; 205 } 206 207 /** 208 * {@inheritDoc} 209 */ 210 @Override 211 public void onReceive(Context context, Intent intent) { 212 if (intent.getAction().equalsIgnoreCase(ConnectivityManager.CONNECTIVITY_ACTION)) { 213 Log.i(LOG_TAG, "ConnectivityManager state change: " + intent.getAction()); 214 synchronized (this) { 215 this.notify(); 216 } 217 } 218 } 219 220 /** 221 * Gets the current state of WiFi. 222 * 223 * @return Returns true if WiFi is on, false otherwise. 224 */ 225 public boolean getWiFiIsOn() { 226 ConnectivityManager connManager = (ConnectivityManager)mContext.getSystemService( 227 Context.CONNECTIVITY_SERVICE); 228 NetworkInfo info = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); 229 Log.i(LOG_TAG, "WiFi Connection state is currently: " + info.isConnected()); 230 return info.isConnected(); 231 } 232 } 233 234 /** 235 * {@inheritDoc} 236 */ 237 @Override 238 public void setUp() throws Exception { 239 mContext = getInstrumentation().getContext(); 240 mDownloadManager = (DownloadManager)mContext.getSystemService(Context.DOWNLOAD_SERVICE); 241 mServer = new MockWebServer(); 242 mServer.play(); 243 mReceiver = registerNewMultipleDownloadsReceiver(); 244 // Note: callers overriding this should call mServer.play() with the desired port # 245 } 246 247 /** 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 552 protected void setAirplaneModeOn(boolean enable) throws Exception { 553 int state = enable ? 1 : 0; 554 555 // Change the system setting 556 Settings.System.putInt(mContext.getContentResolver(), Settings.System.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.System.getInt(mContext.getContentResolver(), 565 Settings.System.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.sendBroadcast(intent); 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 919 protected long doStandardEnqueue(byte[] body) throws Exception { 920 return enqueueDownloadRequest(body, DOWNLOAD_TO_DOWNLOAD_CACHE_DIR); 921 } 922 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 */ 934 protected long doStandardEnqueue(File body) throws Exception { 935 return enqueueDownloadRequest(body, DOWNLOAD_TO_DOWNLOAD_CACHE_DIR); 936 } 937 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 */ 948 protected long doCommonStandardEnqueue() throws Exception { 949 return doEnqueue(DOWNLOAD_TO_DOWNLOAD_CACHE_DIR); 950 } 951 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 */ 969 protected void verifyInt(Cursor cursor, String columnName, int expected) { 970 int index = cursor.getColumnIndex(columnName); 971 int actual = cursor.getInt(index); 972 assertEquals(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 */ 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 */ 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 */ 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