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