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