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 com.android.frameworks.downloadmanagertests; 18 19import android.app.DownloadManager; 20import android.app.DownloadManager.Query; 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.wifi.WifiManager; 29import android.os.Environment; 30import android.os.ParcelFileDescriptor; 31import android.os.SystemClock; 32import android.provider.Settings; 33import android.test.InstrumentationTestCase; 34import android.util.Log; 35 36import java.io.File; 37import java.util.Collections; 38import java.util.HashSet; 39import java.util.Set; 40import java.util.concurrent.TimeoutException; 41 42/** 43 * Base class for Instrumented tests for the Download Manager. 44 */ 45public class DownloadManagerBaseTest extends InstrumentationTestCase { 46 47 protected DownloadManager mDownloadManager = null; 48 protected String mFileType = "text/plain"; 49 protected Context mContext = null; 50 protected MultipleDownloadsCompletedReceiver mReceiver = null; 51 protected static final int DEFAULT_FILE_SIZE = 10 * 1024; // 10kb 52 protected static final int FILE_BLOCK_READ_SIZE = 1024 * 1024; 53 54 protected static final String LOG_TAG = "android.net.DownloadManagerBaseTest"; 55 protected static final int HTTP_OK = 200; 56 protected static final int HTTP_REDIRECT = 307; 57 protected static final int HTTP_PARTIAL_CONTENT = 206; 58 protected static final int HTTP_NOT_FOUND = 404; 59 protected static final int HTTP_SERVICE_UNAVAILABLE = 503; 60 61 protected static final int DEFAULT_MAX_WAIT_TIME = 2 * 60 * 1000; // 2 minutes 62 protected static final int DEFAULT_WAIT_POLL_TIME = 5 * 1000; // 5 seconds 63 64 protected static final int WAIT_FOR_DOWNLOAD_POLL_TIME = 1 * 1000; // 1 second 65 protected static final int MAX_WAIT_FOR_DOWNLOAD_TIME = 5 * 60 * 1000; // 5 minutes 66 protected static final int MAX_WAIT_FOR_LARGE_DOWNLOAD_TIME = 15 * 60 * 1000; // 15 minutes 67 68 public static class MultipleDownloadsCompletedReceiver extends BroadcastReceiver { 69 private volatile int mNumDownloadsCompleted = 0; 70 private Set<Long> downloadIds = Collections.synchronizedSet(new HashSet<Long>()); 71 72 /** 73 * {@inheritDoc} 74 */ 75 @Override 76 public void onReceive(Context context, Intent intent) { 77 if (intent.getAction().equalsIgnoreCase(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) { 78 synchronized(this) { 79 long id = intent.getExtras().getLong(DownloadManager.EXTRA_DOWNLOAD_ID); 80 Log.i(LOG_TAG, "Received Notification for download: " + id); 81 if (!downloadIds.contains(id)) { 82 ++mNumDownloadsCompleted; 83 Log.i(LOG_TAG, "MultipleDownloadsCompletedReceiver got intent: " + 84 intent.getAction() + " --> total count: " + mNumDownloadsCompleted); 85 downloadIds.add(id); 86 87 DownloadManager dm = (DownloadManager)context.getSystemService( 88 Context.DOWNLOAD_SERVICE); 89 90 Cursor cursor = dm.query(new Query().setFilterById(id)); 91 try { 92 if (cursor.moveToFirst()) { 93 int status = cursor.getInt(cursor.getColumnIndex( 94 DownloadManager.COLUMN_STATUS)); 95 Log.i(LOG_TAG, "Download status is: " + status); 96 } else { 97 fail("No status found for completed download!"); 98 } 99 } finally { 100 cursor.close(); 101 } 102 } else { 103 Log.i(LOG_TAG, "Notification for id: " + id + " has already been made."); 104 } 105 } 106 } 107 } 108 109 /** 110 * Gets the number of times the {@link #onReceive} callback has been called for the 111 * {@link DownloadManager#ACTION_DOWNLOAD_COMPLETE} action, indicating the number of 112 * downloads completed thus far. 113 * 114 * @return the number of downloads completed so far. 115 */ 116 public int numDownloadsCompleted() { 117 return mNumDownloadsCompleted; 118 } 119 120 /** 121 * Gets the list of download IDs. 122 * @return A Set<Long> with the ids of the completed downloads. 123 */ 124 public Set<Long> getDownloadIds() { 125 synchronized(this) { 126 Set<Long> returnIds = new HashSet<Long>(downloadIds); 127 return returnIds; 128 } 129 } 130 131 } 132 133 public static class WiFiChangedReceiver extends BroadcastReceiver { 134 private Context mContext = null; 135 136 /** 137 * Constructor 138 * 139 * Sets the current state of WiFi. 140 * 141 * @param context The current app {@link Context}. 142 */ 143 public WiFiChangedReceiver(Context context) { 144 mContext = context; 145 } 146 147 /** 148 * {@inheritDoc} 149 */ 150 @Override 151 public void onReceive(Context context, Intent intent) { 152 if (intent.getAction().equalsIgnoreCase(ConnectivityManager.CONNECTIVITY_ACTION)) { 153 Log.i(LOG_TAG, "ConnectivityManager state change: " + intent.getAction()); 154 synchronized (this) { 155 this.notify(); 156 } 157 } 158 } 159 160 /** 161 * Gets the current state of WiFi. 162 * 163 * @return Returns true if WiFi is on, false otherwise. 164 */ 165 public boolean getWiFiIsOn() { 166 ConnectivityManager connManager = (ConnectivityManager)mContext.getSystemService( 167 Context.CONNECTIVITY_SERVICE); 168 NetworkInfo info = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); 169 Log.i(LOG_TAG, "WiFi Connection state is currently: " + info.isConnected()); 170 return info.isConnected(); 171 } 172 } 173 174 /** 175 * {@inheritDoc} 176 */ 177 @Override 178 public void setUp() throws Exception { 179 mContext = getInstrumentation().getContext(); 180 mDownloadManager = (DownloadManager)mContext.getSystemService(Context.DOWNLOAD_SERVICE); 181 mReceiver = registerNewMultipleDownloadsReceiver(); 182 } 183 184 /** 185 * Helper to verify the size of a file. 186 * 187 * @param pfd The input file to compare the size of 188 * @param size The expected size of the file 189 */ 190 protected void verifyFileSize(ParcelFileDescriptor pfd, long size) { 191 assertEquals(pfd.getStatSize(), size); 192 } 193 194 /** 195 * Helper to create and register a new MultipleDownloadCompletedReciever 196 * 197 * This is used to track many simultaneous downloads by keeping count of all the downloads 198 * that have completed. 199 * 200 * @return A new receiver that records and can be queried on how many downloads have completed. 201 */ 202 protected MultipleDownloadsCompletedReceiver registerNewMultipleDownloadsReceiver() { 203 MultipleDownloadsCompletedReceiver receiver = new MultipleDownloadsCompletedReceiver(); 204 mContext.registerReceiver(receiver, new IntentFilter( 205 DownloadManager.ACTION_DOWNLOAD_COMPLETE)); 206 return receiver; 207 } 208 209 /** 210 * Enables or disables WiFi. 211 * 212 * Note: Needs the following permissions: 213 * android.permission.ACCESS_WIFI_STATE 214 * android.permission.CHANGE_WIFI_STATE 215 * @param enable true if it should be enabled, false if it should be disabled 216 */ 217 protected void setWiFiStateOn(boolean enable) throws Exception { 218 Log.i(LOG_TAG, "Setting WiFi State to: " + enable); 219 WifiManager manager = (WifiManager)mContext.getSystemService(Context.WIFI_SERVICE); 220 221 manager.setWifiEnabled(enable); 222 223 String timeoutMessage = "Timed out waiting for Wifi to be " 224 + (enable ? "enabled!" : "disabled!"); 225 226 WiFiChangedReceiver receiver = new WiFiChangedReceiver(mContext); 227 mContext.registerReceiver(receiver, new IntentFilter( 228 ConnectivityManager.CONNECTIVITY_ACTION)); 229 230 synchronized (receiver) { 231 long timeoutTime = SystemClock.elapsedRealtime() + DEFAULT_MAX_WAIT_TIME; 232 boolean timedOut = false; 233 234 while (receiver.getWiFiIsOn() != enable && !timedOut) { 235 try { 236 receiver.wait(DEFAULT_WAIT_POLL_TIME); 237 238 if (SystemClock.elapsedRealtime() > timeoutTime) { 239 timedOut = true; 240 } 241 } 242 catch (InterruptedException e) { 243 // ignore InterruptedExceptions 244 } 245 } 246 if (timedOut) { 247 fail(timeoutMessage); 248 } 249 } 250 assertEquals(enable, receiver.getWiFiIsOn()); 251 } 252 253 /** 254 * Helper to enables or disables airplane mode. If successful, it also broadcasts an intent 255 * indicating that the mode has changed. 256 * 257 * Note: Needs the following permission: 258 * android.permission.WRITE_SETTINGS 259 * @param enable true if airplane mode should be ON, false if it should be OFF 260 */ 261 protected void setAirplaneModeOn(boolean enable) throws Exception { 262 int state = enable ? 1 : 0; 263 264 // Change the system setting 265 Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 266 state); 267 268 String timeoutMessage = "Timed out waiting for airplane mode to be " + 269 (enable ? "enabled!" : "disabled!"); 270 271 // wait for airplane mode to change state 272 int currentWaitTime = 0; 273 while (Settings.System.getInt(mContext.getContentResolver(), 274 Settings.Global.AIRPLANE_MODE_ON, -1) != state) { 275 timeoutWait(currentWaitTime, DEFAULT_WAIT_POLL_TIME, DEFAULT_MAX_WAIT_TIME, 276 timeoutMessage); 277 } 278 279 // Post the intent 280 Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); 281 intent.putExtra("state", true); 282 mContext.sendBroadcast(intent); 283 } 284 285 /** 286 * Helper to wait for a particular download to finish, or else a timeout to occur 287 * 288 * Does not wait for a receiver notification of the download. 289 * 290 * @param id The download id to query on (wait for) 291 */ 292 protected void waitForDownloadOrTimeout_skipNotification(long id) throws TimeoutException, 293 InterruptedException { 294 doWaitForDownloadsOrTimeout(new Query().setFilterById(id), 295 WAIT_FOR_DOWNLOAD_POLL_TIME, MAX_WAIT_FOR_DOWNLOAD_TIME); 296 } 297 298 /** 299 * Helper to wait for a particular download to finish, or else a timeout to occur 300 * 301 * Also guarantees a notification has been posted for the download. 302 * 303 * @param id The download id to query on (wait for) 304 */ 305 protected void waitForDownloadOrTimeout(long id) throws TimeoutException, 306 InterruptedException { 307 waitForDownloadOrTimeout(id, WAIT_FOR_DOWNLOAD_POLL_TIME, MAX_WAIT_FOR_DOWNLOAD_TIME); 308 } 309 310 /** 311 * Helper to wait for a particular download to finish, or else a timeout to occur 312 * 313 * Also guarantees a notification has been posted for the download. 314 * 315 * @param id The download id to query on (wait for) 316 * @param poll The amount of time to wait 317 * @param timeoutMillis The max time (in ms) to wait for the download(s) to complete 318 */ 319 protected void waitForDownloadOrTimeout(long id, long poll, long timeoutMillis) 320 throws TimeoutException, InterruptedException { 321 doWaitForDownloadsOrTimeout(new Query().setFilterById(id), poll, timeoutMillis); 322 waitForReceiverNotifications(1); 323 } 324 325 /** 326 * Helper to wait for all downloads to finish, or else a specified timeout to occur 327 * 328 * Makes no guaranee that notifications have been posted for all downloads. 329 * 330 * @param poll The amount of time to wait 331 * @param timeoutMillis The max time (in ms) to wait for the download(s) to complete 332 */ 333 protected void waitForDownloadsOrTimeout(long poll, long timeoutMillis) throws TimeoutException, 334 InterruptedException { 335 doWaitForDownloadsOrTimeout(new Query(), poll, timeoutMillis); 336 } 337 338 /** 339 * Helper to wait for all downloads to finish, or else a timeout to occur, but does not throw 340 * 341 * Also guarantees a notification has been posted for the download. 342 * 343 * @param id The id of the download to query against 344 * @param poll The amount of time to wait 345 * @param timeoutMillis The max time (in ms) to wait for the download(s) to complete 346 * @return true if download completed successfully (didn't timeout), false otherwise 347 */ 348 private boolean waitForDownloadOrTimeoutNoThrow(long id, long poll, long timeoutMillis) { 349 try { 350 doWaitForDownloadsOrTimeout(new Query().setFilterById(id), poll, timeoutMillis); 351 waitForReceiverNotifications(1); 352 } catch (TimeoutException e) { 353 return false; 354 } 355 return true; 356 } 357 358 /** 359 * Helper function to synchronously wait, or timeout if the maximum threshold has been exceeded. 360 * 361 * @param currentTotalWaitTime The total time waited so far 362 * @param poll The amount of time to wait 363 * @param maxTimeoutMillis The total wait time threshold; if we've waited more than this long, 364 * we timeout and fail 365 * @param timedOutMessage The message to display in the failure message if we timeout 366 * @return The new total amount of time we've waited so far 367 * @throws TimeoutException if timed out waiting for SD card to mount 368 */ 369 private int timeoutWait(int currentTotalWaitTime, long poll, long maxTimeoutMillis, 370 String timedOutMessage) throws TimeoutException { 371 long now = SystemClock.elapsedRealtime(); 372 long end = now + poll; 373 374 // if we get InterruptedException's, ignore them and just keep sleeping 375 while (now < end) { 376 try { 377 Thread.sleep(end - now); 378 } catch (InterruptedException e) { 379 // ignore interrupted exceptions 380 } 381 now = SystemClock.elapsedRealtime(); 382 } 383 384 currentTotalWaitTime += poll; 385 if (currentTotalWaitTime > maxTimeoutMillis) { 386 throw new TimeoutException(timedOutMessage); 387 } 388 return currentTotalWaitTime; 389 } 390 391 /** 392 * Helper to wait for all downloads to finish, or else a timeout to occur 393 * 394 * @param query The query to pass to the download manager 395 * @param poll The poll time to wait between checks 396 * @param timeoutMillis The max amount of time (in ms) to wait for the download(s) to complete 397 */ 398 private void doWaitForDownloadsOrTimeout(Query query, long poll, long timeoutMillis) 399 throws TimeoutException { 400 int currentWaitTime = 0; 401 while (true) { 402 query.setFilterByStatus(DownloadManager.STATUS_PENDING | DownloadManager.STATUS_PAUSED 403 | DownloadManager.STATUS_RUNNING); 404 Cursor cursor = mDownloadManager.query(query); 405 406 try { 407 if (cursor.getCount() == 0) { 408 Log.i(LOG_TAG, "All downloads should be done..."); 409 break; 410 } 411 currentWaitTime = timeoutWait(currentWaitTime, poll, timeoutMillis, 412 "Timed out waiting for all downloads to finish"); 413 } finally { 414 cursor.close(); 415 } 416 } 417 } 418 419 /** 420 * Synchronously waits for external store to be mounted (eg: SD Card). 421 * 422 * @throws InterruptedException if interrupted 423 * @throws Exception if timed out waiting for SD card to mount 424 */ 425 protected void waitForExternalStoreMount() throws Exception { 426 String extStorageState = Environment.getExternalStorageState(); 427 int currentWaitTime = 0; 428 while (!extStorageState.equals(Environment.MEDIA_MOUNTED)) { 429 Log.i(LOG_TAG, "Waiting for SD card..."); 430 currentWaitTime = timeoutWait(currentWaitTime, DEFAULT_WAIT_POLL_TIME, 431 DEFAULT_MAX_WAIT_TIME, "Timed out waiting for SD Card to be ready!"); 432 extStorageState = Environment.getExternalStorageState(); 433 } 434 } 435 436 /** 437 * Synchronously waits for a download to start. 438 * 439 * @param dlRequest the download request id used by Download Manager to track the download. 440 * @throws Exception if timed out while waiting for SD card to mount 441 */ 442 protected void waitForDownloadToStart(long dlRequest) throws Exception { 443 Cursor cursor = getCursor(dlRequest); 444 try { 445 int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS); 446 int value = cursor.getInt(columnIndex); 447 int currentWaitTime = 0; 448 449 while (value != DownloadManager.STATUS_RUNNING && 450 (value != DownloadManager.STATUS_FAILED) && 451 (value != DownloadManager.STATUS_SUCCESSFUL)) { 452 Log.i(LOG_TAG, "Waiting for download to start..."); 453 currentWaitTime = timeoutWait(currentWaitTime, WAIT_FOR_DOWNLOAD_POLL_TIME, 454 MAX_WAIT_FOR_DOWNLOAD_TIME, "Timed out waiting for download to start!"); 455 cursor.requery(); 456 assertTrue(cursor.moveToFirst()); 457 columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS); 458 value = cursor.getInt(columnIndex); 459 } 460 assertFalse("Download failed immediately after start", 461 value == DownloadManager.STATUS_FAILED); 462 } finally { 463 cursor.close(); 464 } 465 } 466 467 /** 468 * Synchronously waits for our receiver to receive notification for a given number of 469 * downloads. 470 * 471 * @param targetNumber The number of notifications for unique downloads to wait for; pass in 472 * -1 to not wait for notification. 473 * @throws Exception if timed out while waiting 474 */ 475 private void waitForReceiverNotifications(int targetNumber) throws TimeoutException { 476 int count = mReceiver.numDownloadsCompleted(); 477 int currentWaitTime = 0; 478 479 while (count < targetNumber) { 480 Log.i(LOG_TAG, "Waiting for notification of downloads..."); 481 currentWaitTime = timeoutWait(currentWaitTime, WAIT_FOR_DOWNLOAD_POLL_TIME, 482 MAX_WAIT_FOR_DOWNLOAD_TIME, "Timed out waiting for download notifications!" 483 + " Received " + count + "notifications."); 484 count = mReceiver.numDownloadsCompleted(); 485 } 486 } 487 488 /** 489 * Synchronously waits for a file to increase in size (such as to monitor that a download is 490 * progressing). 491 * 492 * @param file The file whose size to track. 493 * @throws Exception if timed out while waiting for the file to grow in size. 494 */ 495 protected void waitForFileToGrow(File file) throws Exception { 496 int currentWaitTime = 0; 497 498 // File may not even exist yet, so wait until it does (or we timeout) 499 while (!file.exists()) { 500 Log.i(LOG_TAG, "Waiting for file to exist..."); 501 currentWaitTime = timeoutWait(currentWaitTime, WAIT_FOR_DOWNLOAD_POLL_TIME, 502 MAX_WAIT_FOR_DOWNLOAD_TIME, "Timed out waiting for file to be created."); 503 } 504 505 // Get original file size... 506 long originalSize = file.length(); 507 508 while (file.length() <= originalSize) { 509 Log.i(LOG_TAG, "Waiting for file to be written to..."); 510 currentWaitTime = timeoutWait(currentWaitTime, WAIT_FOR_DOWNLOAD_POLL_TIME, 511 MAX_WAIT_FOR_DOWNLOAD_TIME, "Timed out waiting for file to be written to."); 512 } 513 } 514 515 /** 516 * Helper to remove all downloads that are registered with the DL Manager. 517 * 518 * Note: This gives us a clean slate b/c it includes downloads that are pending, running, 519 * paused, or have completed. 520 */ 521 protected void removeAllCurrentDownloads() { 522 Log.i(LOG_TAG, "Removing all current registered downloads..."); 523 Cursor cursor = mDownloadManager.query(new Query()); 524 try { 525 if (cursor.moveToFirst()) { 526 do { 527 int index = cursor.getColumnIndex(DownloadManager.COLUMN_ID); 528 long downloadId = cursor.getLong(index); 529 530 mDownloadManager.remove(downloadId); 531 } while (cursor.moveToNext()); 532 } 533 } finally { 534 cursor.close(); 535 } 536 } 537 538 /** 539 * Helper to verify an int value in a Cursor 540 * 541 * @param cursor The cursor containing the query results 542 * @param columnName The name of the column to query 543 * @param expected The expected int value 544 */ 545 private void verifyInt(Cursor cursor, String columnName, int expected) { 546 int index = cursor.getColumnIndex(columnName); 547 int actual = cursor.getInt(index); 548 assertEquals(expected, actual); 549 } 550 551 /** 552 * Performs a query based on ID and returns a Cursor for the query. 553 * 554 * @param id The id of the download in DL Manager; pass -1 to query all downloads 555 * @return A cursor for the query results 556 */ 557 protected Cursor getCursor(long id) throws Exception { 558 Query query = new Query(); 559 if (id != -1) { 560 query.setFilterById(id); 561 } 562 563 Cursor cursor = mDownloadManager.query(query); 564 int currentWaitTime = 0; 565 566 try { 567 while (!cursor.moveToFirst()) { 568 Thread.sleep(DEFAULT_WAIT_POLL_TIME); 569 currentWaitTime += DEFAULT_WAIT_POLL_TIME; 570 if (currentWaitTime > DEFAULT_MAX_WAIT_TIME) { 571 fail("timed out waiting for a non-null query result"); 572 } 573 cursor.requery(); 574 } 575 } catch (Exception e) { 576 cursor.close(); 577 throw e; 578 } 579 return cursor; 580 } 581} 582