Home | History | Annotate | Download | only in downloadmanagertests
      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