1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.app; 18 19 import android.app.DownloadManager.Query; 20 import android.app.DownloadManager.Request; 21 import android.content.BroadcastReceiver; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.IntentFilter; 25 import android.database.Cursor; 26 import android.net.ConnectivityManager; 27 import android.net.NetworkInfo; 28 import android.net.Uri; 29 import android.net.wifi.WifiManager; 30 import android.os.Environment; 31 import android.os.ParcelFileDescriptor; 32 import android.os.UserHandle; 33 import android.os.ParcelFileDescriptor.AutoCloseInputStream; 34 import android.os.SystemClock; 35 import android.provider.Settings; 36 import android.test.InstrumentationTestCase; 37 import android.util.Log; 38 39 import com.google.mockwebserver.MockResponse; 40 import com.google.mockwebserver.MockWebServer; 41 42 import java.io.DataInputStream; 43 import java.io.DataOutputStream; 44 import java.io.File; 45 import java.io.FileInputStream; 46 import java.io.FileNotFoundException; 47 import java.io.FileOutputStream; 48 import java.io.IOException; 49 import java.net.URL; 50 import java.util.ArrayList; 51 import java.util.Collections; 52 import java.util.HashSet; 53 import java.util.Random; 54 import java.util.Set; 55 import java.util.concurrent.TimeoutException; 56 57 import libcore.io.Streams; 58 59 /** 60 * Base class for Instrumented tests for the Download Manager. 61 */ 62 public class DownloadManagerBaseTest extends InstrumentationTestCase { 63 private static final String TAG = "DownloadManagerBaseTest"; 64 protected DownloadManager mDownloadManager = null; 65 private MockWebServer mServer = null; 66 protected String mFileType = "text/plain"; 67 protected Context mContext = null; 68 protected MultipleDownloadsCompletedReceiver mReceiver = null; 69 protected static final int DEFAULT_FILE_SIZE = 10 * 1024; // 10kb 70 protected static final int FILE_BLOCK_READ_SIZE = 1024 * 1024; 71 72 protected static final String LOG_TAG = "android.net.DownloadManagerBaseTest"; 73 protected static final int HTTP_OK = 200; 74 protected static final int HTTP_REDIRECT = 307; 75 protected static final int HTTP_PARTIAL_CONTENT = 206; 76 protected static final int HTTP_NOT_FOUND = 404; 77 protected static final int HTTP_SERVICE_UNAVAILABLE = 503; 78 protected String DEFAULT_FILENAME = "somefile.txt"; 79 80 protected static final int DEFAULT_MAX_WAIT_TIME = 2 * 60 * 1000; // 2 minutes 81 protected static final int DEFAULT_WAIT_POLL_TIME = 5 * 1000; // 5 seconds 82 83 protected static final int WAIT_FOR_DOWNLOAD_POLL_TIME = 1 * 1000; // 1 second 84 protected static final int MAX_WAIT_FOR_DOWNLOAD_TIME = 5 * 60 * 1000; // 5 minutes 85 protected static final int MAX_WAIT_FOR_LARGE_DOWNLOAD_TIME = 15 * 60 * 1000; // 15 minutes 86 87 protected static final int DOWNLOAD_TO_SYSTEM_CACHE = 1; 88 protected static final int DOWNLOAD_TO_DOWNLOAD_CACHE_DIR = 2; 89 90 // Just a few popular file types used to return from a download 91 protected enum DownloadFileType { 92 PLAINTEXT, 93 APK, 94 GIF, 95 GARBAGE, 96 UNRECOGNIZED, 97 ZIP 98 } 99 100 protected enum DataType { 101 TEXT, 102 BINARY 103 } 104 105 public static class LoggingRng extends Random { 106 107 /** 108 * Constructor 109 * 110 * Creates RNG with self-generated seed value. 111 */ 112 public LoggingRng() { 113 this(SystemClock.uptimeMillis()); 114 } 115 116 /** 117 * Constructor 118 * 119 * Creats RNG with given initial seed value 120 121 * @param seed The initial seed value 122 */ 123 public LoggingRng(long seed) { 124 super(seed); 125 Log.i(LOG_TAG, "Seeding RNG with value: " + seed); 126 } 127 } 128 129 public static class MultipleDownloadsCompletedReceiver extends BroadcastReceiver { 130 private volatile int mNumDownloadsCompleted = 0; 131 private Set<Long> downloadIds = Collections.synchronizedSet(new HashSet<Long>()); 132 133 /** 134 * {@inheritDoc} 135 */ 136 @Override 137 public void onReceive(Context context, Intent intent) { 138 if (intent.getAction().equalsIgnoreCase(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) { 139 synchronized(this) { 140 long id = intent.getExtras().getLong(DownloadManager.EXTRA_DOWNLOAD_ID); 141 Log.i(LOG_TAG, "Received Notification for download: " + id); 142 if (!downloadIds.contains(id)) { 143 ++mNumDownloadsCompleted; 144 Log.i(LOG_TAG, "MultipleDownloadsCompletedReceiver got intent: " + 145 intent.getAction() + " --> total count: " + mNumDownloadsCompleted); 146 downloadIds.add(id); 147 148 DownloadManager dm = (DownloadManager)context.getSystemService( 149 Context.DOWNLOAD_SERVICE); 150 151 Cursor cursor = dm.query(new Query().setFilterById(id)); 152 try { 153 if (cursor.moveToFirst()) { 154 int status = cursor.getInt(cursor.getColumnIndex( 155 DownloadManager.COLUMN_STATUS)); 156 Log.i(LOG_TAG, "Download status is: " + status); 157 } else { 158 fail("No status found for completed download!"); 159 } 160 } finally { 161 cursor.close(); 162 } 163 } else { 164 Log.i(LOG_TAG, "Notification for id: " + id + " has already been made."); 165 } 166 } 167 } 168 } 169 170 /** 171 * Gets the number of times the {@link #onReceive} callback has been called for the 172 * {@link DownloadManager.ACTION_DOWNLOAD_COMPLETED} action, indicating the number of 173 * downloads completed thus far. 174 * 175 * @return the number of downloads completed so far. 176 */ 177 public int numDownloadsCompleted() { 178 return mNumDownloadsCompleted; 179 } 180 181 /** 182 * Gets the list of download IDs. 183 * @return A Set<Long> with the ids of the completed downloads. 184 */ 185 public Set<Long> getDownloadIds() { 186 synchronized(this) { 187 Set<Long> returnIds = new HashSet<Long>(downloadIds); 188 return returnIds; 189 } 190 } 191 192 } 193 194 public static class WiFiChangedReceiver extends BroadcastReceiver { 195 private Context mContext = null; 196 197 /** 198 * Constructor 199 * 200 * Sets the current state of WiFi. 201 * 202 * @param context The current app {@link Context}. 203 */ 204 public WiFiChangedReceiver(Context context) { 205 mContext = context; 206 } 207 208 /** 209 * {@inheritDoc} 210 */ 211 @Override 212 public void onReceive(Context context, Intent intent) { 213 if (intent.getAction().equalsIgnoreCase(ConnectivityManager.CONNECTIVITY_ACTION)) { 214 Log.i(LOG_TAG, "ConnectivityManager state change: " + intent.getAction()); 215 synchronized (this) { 216 this.notify(); 217 } 218 } 219 } 220 221 /** 222 * Gets the current state of WiFi. 223 * 224 * @return Returns true if WiFi is on, false otherwise. 225 */ 226 public boolean getWiFiIsOn() { 227 ConnectivityManager connManager = (ConnectivityManager)mContext.getSystemService( 228 Context.CONNECTIVITY_SERVICE); 229 NetworkInfo info = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); 230 Log.i(LOG_TAG, "WiFi Connection state is currently: " + info.isConnected()); 231 return info.isConnected(); 232 } 233 } 234 235 /** 236 * {@inheritDoc} 237 */ 238 @Override 239 public void setUp() throws Exception { 240 mContext = getInstrumentation().getContext(); 241 mDownloadManager = (DownloadManager)mContext.getSystemService(Context.DOWNLOAD_SERVICE); 242 mServer = new MockWebServer(); 243 mServer.play(); 244 mReceiver = registerNewMultipleDownloadsReceiver(); 245 // Note: callers overriding this should call mServer.play() with the desired port # 246 } 247 248 /** 249 * Helper to build a response from the MockWebServer with no body. 250 * 251 * @param status The HTTP status code to return for this response 252 * @return Returns the mock web server response that was queued (which can be modified) 253 */ 254 protected MockResponse buildResponse(int status) { 255 MockResponse response = new MockResponse().setResponseCode(status); 256 response.setHeader("Content-type", mFileType); 257 return response; 258 } 259 260 /** 261 * Helper to build a response from the MockWebServer. 262 * 263 * @param status The HTTP status code to return for this response 264 * @param body The body to return in this response 265 * @return Returns the mock web server response that was queued (which can be modified) 266 */ 267 protected MockResponse buildResponse(int status, byte[] body) { 268 return buildResponse(status).setBody(body); 269 } 270 271 /** 272 * Helper to build a response from the MockWebServer. 273 * 274 * @param status The HTTP status code to return for this response 275 * @param bodyFile The body to return in this response 276 * @return Returns the mock web server response that was queued (which can be modified) 277 */ 278 protected MockResponse buildResponse(int status, File bodyFile) 279 throws FileNotFoundException, IOException { 280 final byte[] body = Streams.readFully(new FileInputStream(bodyFile)); 281 return buildResponse(status).setBody(body); 282 } 283 284 protected void enqueueResponse(MockResponse resp) { 285 mServer.enqueue(resp); 286 } 287 288 /** 289 * Helper to generate a random blob of bytes. 290 * 291 * @param size The size of the data to generate 292 * @param type The type of data to generate: currently, one of {@link DataType#TEXT} or 293 * {@link DataType#BINARY}. 294 * @return The random data that is generated. 295 */ 296 protected byte[] generateData(int size, DataType type) { 297 return generateData(size, type, null); 298 } 299 300 /** 301 * Helper to generate a random blob of bytes using a given RNG. 302 * 303 * @param size The size of the data to generate 304 * @param type The type of data to generate: currently, one of {@link DataType#TEXT} or 305 * {@link DataType#BINARY}. 306 * @param rng (optional) The RNG to use; pass null to use 307 * @return The random data that is generated. 308 */ 309 protected byte[] generateData(int size, DataType type, Random rng) { 310 int min = Byte.MIN_VALUE; 311 int max = Byte.MAX_VALUE; 312 313 // Only use chars in the HTTP ASCII printable character range for Text 314 if (type == DataType.TEXT) { 315 min = 32; 316 max = 126; 317 } 318 byte[] result = new byte[size]; 319 Log.i(LOG_TAG, "Generating data of size: " + size); 320 321 if (rng == null) { 322 rng = new LoggingRng(); 323 } 324 325 for (int i = 0; i < size; ++i) { 326 result[i] = (byte) (min + rng.nextInt(max - min + 1)); 327 } 328 return result; 329 } 330 331 /** 332 * Helper to verify the size of a file. 333 * 334 * @param pfd The input file to compare the size of 335 * @param size The expected size of the file 336 */ 337 protected void verifyFileSize(ParcelFileDescriptor pfd, long size) { 338 assertEquals(pfd.getStatSize(), size); 339 } 340 341 /** 342 * Helper to verify the contents of a downloaded file versus a byte[]. 343 * 344 * @param actual The file of whose contents to verify 345 * @param expected The data we expect to find in the aforementioned file 346 * @throws IOException if there was a problem reading from the file 347 */ 348 protected void verifyFileContents(ParcelFileDescriptor actual, byte[] expected) 349 throws IOException { 350 AutoCloseInputStream input = new ParcelFileDescriptor.AutoCloseInputStream(actual); 351 long fileSize = actual.getStatSize(); 352 353 assertTrue(fileSize <= Integer.MAX_VALUE); 354 assertEquals(expected.length, fileSize); 355 356 byte[] actualData = new byte[expected.length]; 357 assertEquals(input.read(actualData), fileSize); 358 compareByteArrays(actualData, expected); 359 } 360 361 /** 362 * Helper to compare 2 byte arrays. 363 * 364 * @param actual The array whose data we want to verify 365 * @param expected The array of data we expect to see 366 */ 367 protected void compareByteArrays(byte[] actual, byte[] expected) { 368 assertEquals(actual.length, expected.length); 369 int length = actual.length; 370 for (int i = 0; i < length; ++i) { 371 // assert has a bit of overhead, so only do the assert when the values are not the same 372 if (actual[i] != expected[i]) { 373 fail("Byte arrays are not equal."); 374 } 375 } 376 } 377 378 /** 379 * Verifies the contents of a downloaded file versus the contents of a File. 380 * 381 * @param pfd The file whose data we want to verify 382 * @param file The file containing the data we expect to see in the aforementioned file 383 * @throws IOException If there was a problem reading either of the two files 384 */ 385 protected void verifyFileContents(ParcelFileDescriptor pfd, File file) throws IOException { 386 byte[] actual = new byte[FILE_BLOCK_READ_SIZE]; 387 byte[] expected = new byte[FILE_BLOCK_READ_SIZE]; 388 389 AutoCloseInputStream input = new ParcelFileDescriptor.AutoCloseInputStream(pfd); 390 391 assertEquals(file.length(), pfd.getStatSize()); 392 393 DataInputStream inFile = new DataInputStream(new FileInputStream(file)); 394 int actualRead = 0; 395 int expectedRead = 0; 396 397 while (((actualRead = input.read(actual)) != -1) && 398 ((expectedRead = inFile.read(expected)) != -1)) { 399 assertEquals(actualRead, expectedRead); 400 compareByteArrays(actual, expected); 401 } 402 } 403 404 /** 405 * Sets the MIME type of file that will be served from the mock server 406 * 407 * @param type The MIME type to return from the server 408 */ 409 protected void setServerMimeType(DownloadFileType type) { 410 mFileType = getMimeMapping(type); 411 } 412 413 /** 414 * Gets the MIME content string for a given type 415 * 416 * @param type The MIME type to return 417 * @return the String representation of that MIME content type 418 */ 419 protected String getMimeMapping(DownloadFileType type) { 420 switch (type) { 421 case APK: 422 return "application/vnd.android.package-archive"; 423 case GIF: 424 return "image/gif"; 425 case ZIP: 426 return "application/x-zip-compressed"; 427 case GARBAGE: 428 return "zip\\pidy/doo/da"; 429 case UNRECOGNIZED: 430 return "application/new.undefined.type.of.app"; 431 } 432 return "text/plain"; 433 } 434 435 /** 436 * Gets the Uri that should be used to access the mock server 437 * 438 * @param filename The name of the file to try to retrieve from the mock server 439 * @return the Uri to use for access the file on the mock server 440 */ 441 protected Uri getServerUri(String filename) throws Exception { 442 URL url = mServer.getUrl("/" + filename); 443 return Uri.parse(url.toString()); 444 } 445 446 /** 447 * Gets the Uri that should be used to access the mock server 448 * 449 * @param filename The name of the file to try to retrieve from the mock server 450 * @return the Uri to use for access the file on the mock server 451 */ 452 protected void logDBColumnData(Cursor cursor, String column) { 453 int index = cursor.getColumnIndex(column); 454 Log.i(LOG_TAG, "columnName: " + column); 455 Log.i(LOG_TAG, "columnValue: " + cursor.getString(index)); 456 } 457 458 /** 459 * Helper to create and register a new MultipleDownloadCompletedReciever 460 * 461 * This is used to track many simultaneous downloads by keeping count of all the downloads 462 * that have completed. 463 * 464 * @return A new receiver that records and can be queried on how many downloads have completed. 465 */ 466 protected MultipleDownloadsCompletedReceiver registerNewMultipleDownloadsReceiver() { 467 MultipleDownloadsCompletedReceiver receiver = new MultipleDownloadsCompletedReceiver(); 468 mContext.registerReceiver(receiver, new IntentFilter( 469 DownloadManager.ACTION_DOWNLOAD_COMPLETE)); 470 return receiver; 471 } 472 473 /** 474 * Helper to verify a standard single-file download from the mock server, and clean up after 475 * verification 476 * 477 * Note that this also calls the Download manager's remove, which cleans up the file from cache. 478 * 479 * @param requestId The id of the download to remove 480 * @param fileData The data to verify the file contains 481 */ 482 protected void verifyAndCleanupSingleFileDownload(long requestId, byte[] fileData) 483 throws Exception { 484 int fileSize = fileData.length; 485 ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(requestId); 486 Cursor cursor = mDownloadManager.query(new Query().setFilterById(requestId)); 487 488 try { 489 assertEquals(1, cursor.getCount()); 490 assertTrue(cursor.moveToFirst()); 491 492 verifyFileSize(pfd, fileSize); 493 verifyFileContents(pfd, fileData); 494 } finally { 495 pfd.close(); 496 cursor.close(); 497 mDownloadManager.remove(requestId); 498 } 499 } 500 501 /** 502 * Enables or disables WiFi. 503 * 504 * Note: Needs the following permissions: 505 * android.permission.ACCESS_WIFI_STATE 506 * android.permission.CHANGE_WIFI_STATE 507 * @param enable true if it should be enabled, false if it should be disabled 508 */ 509 protected void setWiFiStateOn(boolean enable) throws Exception { 510 Log.i(LOG_TAG, "Setting WiFi State to: " + enable); 511 WifiManager manager = (WifiManager)mContext.getSystemService(Context.WIFI_SERVICE); 512 513 manager.setWifiEnabled(enable); 514 515 String timeoutMessage = "Timed out waiting for Wifi to be " 516 + (enable ? "enabled!" : "disabled!"); 517 518 WiFiChangedReceiver receiver = new WiFiChangedReceiver(mContext); 519 mContext.registerReceiver(receiver, new IntentFilter( 520 ConnectivityManager.CONNECTIVITY_ACTION)); 521 522 synchronized (receiver) { 523 long timeoutTime = SystemClock.elapsedRealtime() + DEFAULT_MAX_WAIT_TIME; 524 boolean timedOut = false; 525 526 while (receiver.getWiFiIsOn() != enable && !timedOut) { 527 try { 528 receiver.wait(DEFAULT_WAIT_POLL_TIME); 529 530 if (SystemClock.elapsedRealtime() > timeoutTime) { 531 timedOut = true; 532 } 533 } 534 catch (InterruptedException e) { 535 // ignore InterruptedExceptions 536 } 537 } 538 if (timedOut) { 539 fail(timeoutMessage); 540 } 541 } 542 assertEquals(enable, receiver.getWiFiIsOn()); 543 } 544 545 /** 546 * Helper to enables or disables airplane mode. If successful, it also broadcasts an intent 547 * indicating that the mode has changed. 548 * 549 * Note: Needs the following permission: 550 * android.permission.WRITE_SETTINGS 551 * @param enable true if airplane mode should be ON, false if it should be OFF 552 */ 553 protected void setAirplaneModeOn(boolean enable) throws Exception { 554 int state = enable ? 1 : 0; 555 556 // Change the system setting 557 Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 558 state); 559 560 String timeoutMessage = "Timed out waiting for airplane mode to be " + 561 (enable ? "enabled!" : "disabled!"); 562 563 // wait for airplane mode to change state 564 int currentWaitTime = 0; 565 while (Settings.Global.getInt(mContext.getContentResolver(), 566 Settings.Global.AIRPLANE_MODE_ON, -1) != state) { 567 timeoutWait(currentWaitTime, DEFAULT_WAIT_POLL_TIME, DEFAULT_MAX_WAIT_TIME, 568 timeoutMessage); 569 } 570 571 // Post the intent 572 Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); 573 intent.putExtra("state", true); 574 mContext.sendBroadcastAsUser(intent, UserHandle.ALL); 575 } 576 577 /** 578 * Helper to create a large file of random data on the SD card. 579 * 580 * @param filename (optional) The name of the file to create on the SD card; pass in null to 581 * use a default temp filename. 582 * @param type The type of file to create 583 * @param subdirectory If not null, the subdirectory under the SD card where the file should go 584 * @return The File that was created 585 * @throws IOException if there was an error while creating the file. 586 */ 587 protected File createFileOnSD(String filename, long fileSize, DataType type, 588 String subdirectory) throws IOException { 589 590 // Build up the file path and name 591 String sdPath = Environment.getExternalStorageDirectory().getPath(); 592 StringBuilder fullPath = new StringBuilder(sdPath); 593 if (subdirectory != null) { 594 fullPath.append(File.separatorChar).append(subdirectory); 595 } 596 597 File file = null; 598 if (filename == null) { 599 file = File.createTempFile("DMTEST_", null, new File(fullPath.toString())); 600 } 601 else { 602 fullPath.append(File.separatorChar).append(filename); 603 file = new File(fullPath.toString()); 604 file.createNewFile(); 605 } 606 607 // Fill the file with random data 608 DataOutputStream output = new DataOutputStream(new FileOutputStream(file)); 609 final int CHUNK_SIZE = 1000000; // copy random data in 1000000-char chunks 610 long remaining = fileSize; 611 int nextChunkSize = CHUNK_SIZE; 612 byte[] randomData = null; 613 Random rng = new LoggingRng(); 614 byte[] chunkSizeData = generateData(nextChunkSize, type, rng); 615 616 try { 617 while (remaining > 0) { 618 if (remaining < CHUNK_SIZE) { 619 nextChunkSize = (int)remaining; 620 remaining = 0; 621 randomData = generateData(nextChunkSize, type, rng); 622 } 623 else { 624 remaining -= CHUNK_SIZE; 625 randomData = chunkSizeData; 626 } 627 output.write(randomData); 628 Log.i(TAG, "while creating " + fileSize + " file, " + 629 "remaining bytes to be written: " + remaining); 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 ArrayList<Long> ids = new ArrayList<Long>(); 889 Cursor cursor = mDownloadManager.query(new Query()); 890 try { 891 if (cursor.moveToFirst()) { 892 do { 893 int index = cursor.getColumnIndex(DownloadManager.COLUMN_ID); 894 long downloadId = cursor.getLong(index); 895 ids.add(downloadId); 896 } while (cursor.moveToNext()); 897 } 898 } finally { 899 cursor.close(); 900 } 901 // delete all ids 902 for (long id : ids) { 903 mDownloadManager.remove(id); 904 } 905 // make sure the database is empty 906 cursor = mDownloadManager.query(new Query()); 907 try { 908 assertEquals(0, cursor.getCount()); 909 } finally { 910 cursor.close(); 911 } 912 } 913 914 /** 915 * Helper to perform a standard enqueue of data to the mock server. 916 * download is performed to the downloads cache dir (NOT systemcache dir) 917 * 918 * @param body The body to return in the response from the server 919 */ 920 protected long doStandardEnqueue(byte[] body) throws Exception { 921 return enqueueDownloadRequest(body, DOWNLOAD_TO_DOWNLOAD_CACHE_DIR); 922 } 923 924 protected long enqueueDownloadRequest(byte[] body, int location) throws Exception { 925 // Prepare the mock server with a standard response 926 mServer.enqueue(buildResponse(HTTP_OK, body)); 927 return doEnqueue(location); 928 } 929 930 /** 931 * Helper to perform a standard enqueue of data to the mock server. 932 * 933 * @param body The body to return in the response from the server, contained in the file 934 */ 935 protected long doStandardEnqueue(File body) throws Exception { 936 return enqueueDownloadRequest(body, DOWNLOAD_TO_DOWNLOAD_CACHE_DIR); 937 } 938 939 protected long enqueueDownloadRequest(File body, int location) throws Exception { 940 // Prepare the mock server with a standard response 941 mServer.enqueue(buildResponse(HTTP_OK, body)); 942 return doEnqueue(location); 943 } 944 945 /** 946 * Helper to do the additional steps (setting title and Uri of default filename) when 947 * doing a standard enqueue request to the server. 948 */ 949 protected long doCommonStandardEnqueue() throws Exception { 950 return doEnqueue(DOWNLOAD_TO_DOWNLOAD_CACHE_DIR); 951 } 952 953 private long doEnqueue(int location) throws Exception { 954 Uri uri = getServerUri(DEFAULT_FILENAME); 955 Request request = new Request(uri).setTitle(DEFAULT_FILENAME); 956 if (location == DOWNLOAD_TO_SYSTEM_CACHE) { 957 request.setDestinationToSystemCache(); 958 } 959 960 return mDownloadManager.enqueue(request); 961 } 962 963 /** 964 * Helper to verify an int value in a Cursor 965 * 966 * @param cursor The cursor containing the query results 967 * @param columnName The name of the column to query 968 * @param expected The expected int value 969 */ 970 protected void verifyInt(Cursor cursor, String columnName, int expected) { 971 int index = cursor.getColumnIndex(columnName); 972 int actual = cursor.getInt(index); 973 assertEquals(expected, actual); 974 } 975 976 /** 977 * Helper to verify a String value in a Cursor 978 * 979 * @param cursor The cursor containing the query results 980 * @param columnName The name of the column to query 981 * @param expected The expected String value 982 */ 983 protected void verifyString(Cursor cursor, String columnName, String expected) { 984 int index = cursor.getColumnIndex(columnName); 985 String actual = cursor.getString(index); 986 Log.i(LOG_TAG, ": " + actual); 987 assertEquals(expected, actual); 988 } 989 990 /** 991 * Performs a query based on ID and returns a Cursor for the query. 992 * 993 * @param id The id of the download in DL Manager; pass -1 to query all downloads 994 * @return A cursor for the query results 995 */ 996 protected Cursor getCursor(long id) throws Exception { 997 Query query = new Query(); 998 if (id != -1) { 999 query.setFilterById(id); 1000 } 1001 1002 Cursor cursor = mDownloadManager.query(query); 1003 int currentWaitTime = 0; 1004 1005 try { 1006 while (!cursor.moveToFirst()) { 1007 Thread.sleep(DEFAULT_WAIT_POLL_TIME); 1008 currentWaitTime += DEFAULT_WAIT_POLL_TIME; 1009 if (currentWaitTime > DEFAULT_MAX_WAIT_TIME) { 1010 fail("timed out waiting for a non-null query result"); 1011 } 1012 cursor.requery(); 1013 } 1014 } catch (Exception e) { 1015 cursor.close(); 1016 throw e; 1017 } 1018 return cursor; 1019 } 1020 1021 /** 1022 * Helper that does the actual basic download verification. 1023 */ 1024 protected long doBasicDownload(byte[] blobData, int location) throws Exception { 1025 long dlRequest = enqueueDownloadRequest(blobData, location); 1026 1027 // wait for the download to complete 1028 waitForDownloadOrTimeout(dlRequest); 1029 1030 assertEquals(1, mReceiver.numDownloadsCompleted()); 1031 return dlRequest; 1032 } 1033 } 1034