/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.frameworks.downloadmanagertests; import android.app.DownloadManager; import android.app.DownloadManager.Query; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.Cursor; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.wifi.WifiManager; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.os.ParcelFileDescriptor; import android.os.SystemClock; import android.provider.Settings; import android.test.InstrumentationTestCase; import android.util.Log; import java.util.HashSet; import java.util.Set; import java.util.concurrent.TimeoutException; /** * Base class for Instrumented tests for the Download Manager. */ public class DownloadManagerBaseTest extends InstrumentationTestCase { protected DownloadManager mDownloadManager = null; protected String mFileType = "text/plain"; protected Context mContext = null; protected static final int DEFAULT_FILE_SIZE = 10 * 1024; // 10kb protected static final int FILE_BLOCK_READ_SIZE = 1024 * 1024; protected static final String LOG_TAG = "android.net.DownloadManagerBaseTest"; protected static final int HTTP_OK = 200; protected static final int HTTP_REDIRECT = 307; protected static final int HTTP_PARTIAL_CONTENT = 206; protected static final int HTTP_NOT_FOUND = 404; protected static final int HTTP_SERVICE_UNAVAILABLE = 503; protected static final int DEFAULT_MAX_WAIT_TIME = 2 * 60 * 1000; // 2 minutes protected static final int DEFAULT_WAIT_POLL_TIME = 5 * 1000; // 5 seconds protected static final int WAIT_FOR_DOWNLOAD_POLL_TIME = 1 * 1000; // 1 second protected static final int MAX_WAIT_FOR_DOWNLOAD_TIME = 5 * 60 * 1000; // 5 minutes protected static final int MAX_WAIT_FOR_LARGE_DOWNLOAD_TIME = 15 * 60 * 1000; // 15 minutes private DownloadFinishedListener mListener; private Thread mListenerThread; public static class WiFiChangedReceiver extends BroadcastReceiver { private Context mContext = null; /** * Constructor * * Sets the current state of WiFi. * * @param context The current app {@link Context}. */ public WiFiChangedReceiver(Context context) { mContext = context; } /** * {@inheritDoc} */ @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equalsIgnoreCase(ConnectivityManager.CONNECTIVITY_ACTION)) { Log.i(LOG_TAG, "ConnectivityManager state change: " + intent.getAction()); synchronized (this) { this.notify(); } } } /** * Gets the current state of WiFi. * * @return Returns true if WiFi is on, false otherwise. */ public boolean getWiFiIsOn() { ConnectivityManager connManager = (ConnectivityManager)mContext.getSystemService( Context.CONNECTIVITY_SERVICE); NetworkInfo info = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); Log.i(LOG_TAG, "WiFi Connection state is currently: " + info.isConnected()); return info.isConnected(); } } /** * Broadcast receiver to listen for broadcast from DownloadManager indicating that downloads * are finished. */ private class DownloadFinishedListener extends BroadcastReceiver implements Runnable { private Handler mHandler = null; private Looper mLooper; private Set<Long> mFinishedDownloads = new HashSet<Long>(); /** * Event loop for the thread that listens to broadcasts. */ @Override public void run() { Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); mHandler = new Handler(); notifyAll(); } Looper.loop(); } /** * Handles the incoming notifications from DownloadManager. */ @Override public void onReceive(Context context, Intent intent) { if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) { long id = intent.getExtras().getLong(DownloadManager.EXTRA_DOWNLOAD_ID); Log.i(LOG_TAG, "Received Notification for download: " + id); synchronized (this) { if(!mFinishedDownloads.contains(id)) { mFinishedDownloads.add(id); notifyAll(); } else { Log.i(LOG_TAG, String.format("Notification for %d was already received", id)); } } } } /** * Returns the handler for this thread. Need this to make sure that the events are handled * in it is own thread and don't interfere with the instrumentation thread. * @return Handler for the receiver thread. * @throws InterruptedException */ private Handler getHandler() throws InterruptedException { synchronized (this) { if (mHandler != null) return mHandler; while (mHandler == null) { wait(); } return mHandler; } } /** * Stops the thread that receives notification from DownloadManager. */ public void cancel() { synchronized(this) { if (mLooper != null) { mLooper.quit(); } } } /** * Waits for a given download to finish, or until the timeout expires. * @param id id of the download to wait for. * @param timeout maximum time to wait, in milliseconds * @return true if the download finished, false otherwise. * @throws InterruptedException */ public boolean waitForDownloadToFinish(long id, long timeout) throws InterruptedException { long startTime = SystemClock.uptimeMillis(); synchronized (this) { while (!mFinishedDownloads.contains(id)) { if (SystemClock.uptimeMillis() - startTime > timeout) { Log.i(LOG_TAG, String.format("Timeout while waiting for %d to finish", id)); return false; } else { wait(timeout); } } return true; } } /** * Waits for multiple downloads to finish, or until timeout expires. * @param ids ids of the downloads to wait for. * @param timeout maximum time to wait, in milliseconds * @return true of all the downloads finished, false otherwise. * @throws InterruptedException */ public boolean waitForMultipleDownloadsToFinish(Set<Long> ids, long timeout) throws InterruptedException { long startTime = SystemClock.uptimeMillis(); synchronized (this) { while (!mFinishedDownloads.containsAll(ids)) { if (SystemClock.uptimeMillis() - startTime > timeout) { Log.i(LOG_TAG, "Timeout waiting for multiple downloads to finish"); return false; } else { wait(timeout); } } return true; } } } /** * {@inheritDoc} */ @Override public void setUp() throws Exception { super.setUp(); mContext = getInstrumentation().getContext(); mDownloadManager = (DownloadManager)mContext.getSystemService(Context.DOWNLOAD_SERVICE); mListener = registerDownloadsListener(); } @Override public void tearDown() throws Exception { mContext.unregisterReceiver(mListener); mListener.cancel(); mListenerThread.join(); super.tearDown(); } /** * Helper to verify the size of a file. * * @param pfd The input file to compare the size of * @param size The expected size of the file */ protected void verifyFileSize(ParcelFileDescriptor pfd, long size) { assertEquals(pfd.getStatSize(), size); } /** * Helper to create and register a new MultipleDownloadCompletedReciever * * This is used to track many simultaneous downloads by keeping count of all the downloads * that have completed. * * @return A new receiver that records and can be queried on how many downloads have completed. * @throws InterruptedException */ protected DownloadFinishedListener registerDownloadsListener() throws InterruptedException { DownloadFinishedListener listener = new DownloadFinishedListener(); mListenerThread = new Thread(listener); mListenerThread.start(); mContext.registerReceiver(listener, new IntentFilter( DownloadManager.ACTION_DOWNLOAD_COMPLETE), null, listener.getHandler()); return listener; } /** * Enables or disables WiFi. * * Note: Needs the following permissions: * android.permission.ACCESS_WIFI_STATE * android.permission.CHANGE_WIFI_STATE * @param enable true if it should be enabled, false if it should be disabled */ protected void setWiFiStateOn(boolean enable) throws Exception { Log.i(LOG_TAG, "Setting WiFi State to: " + enable); WifiManager manager = (WifiManager)mContext.getSystemService(Context.WIFI_SERVICE); manager.setWifiEnabled(enable); String timeoutMessage = "Timed out waiting for Wifi to be " + (enable ? "enabled!" : "disabled!"); WiFiChangedReceiver receiver = new WiFiChangedReceiver(mContext); mContext.registerReceiver(receiver, new IntentFilter( ConnectivityManager.CONNECTIVITY_ACTION)); synchronized (receiver) { long timeoutTime = SystemClock.elapsedRealtime() + DEFAULT_MAX_WAIT_TIME; boolean timedOut = false; while (receiver.getWiFiIsOn() != enable && !timedOut) { try { receiver.wait(DEFAULT_WAIT_POLL_TIME); if (SystemClock.elapsedRealtime() > timeoutTime) { timedOut = true; } } catch (InterruptedException e) { // ignore InterruptedExceptions } } if (timedOut) { fail(timeoutMessage); } } assertEquals(enable, receiver.getWiFiIsOn()); } /** * Helper to enables or disables airplane mode. If successful, it also broadcasts an intent * indicating that the mode has changed. * * Note: Needs the following permission: * android.permission.WRITE_SETTINGS * @param enable true if airplane mode should be ON, false if it should be OFF */ protected void setAirplaneModeOn(boolean enable) throws Exception { int state = enable ? 1 : 0; // Change the system setting Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, state); String timeoutMessage = "Timed out waiting for airplane mode to be " + (enable ? "enabled!" : "disabled!"); // wait for airplane mode to change state int currentWaitTime = 0; while (Settings.System.getInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, -1) != state) { timeoutWait(currentWaitTime, DEFAULT_WAIT_POLL_TIME, DEFAULT_MAX_WAIT_TIME, timeoutMessage); } // Post the intent Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); intent.putExtra("state", true); mContext.sendBroadcast(intent); } /** * Helper to wait for a particular download to finish, or else a timeout to occur. * * @param id The download id to query on (wait for) * @param poll The amount of time to wait * @param timeoutMillis The max time (in ms) to wait for the download(s) to complete */ protected boolean waitForDownload(long id, long timeoutMillis) throws InterruptedException { return mListener.waitForDownloadToFinish(id, timeoutMillis); } protected boolean waitForMultipleDownloads(Set<Long> ids, long timeout) throws InterruptedException { return mListener.waitForMultipleDownloadsToFinish(ids, timeout); } /** * Checks with the download manager if the give download is finished. * @param id id of the download to check * @return true if download is finished, false otherwise. */ private boolean hasDownloadFinished(long id) { Query q = new Query(); q.setFilterById(id); q.setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL); Cursor cursor = mDownloadManager.query(q); boolean finished = cursor.getCount() == 1; cursor.close(); return finished; } /** * Helper function to synchronously wait, or timeout if the maximum threshold has been exceeded. * * @param currentTotalWaitTime The total time waited so far * @param poll The amount of time to wait * @param maxTimeoutMillis The total wait time threshold; if we've waited more than this long, * we timeout and fail * @param timedOutMessage The message to display in the failure message if we timeout * @return The new total amount of time we've waited so far * @throws TimeoutException if timed out waiting for SD card to mount */ private int timeoutWait(int currentTotalWaitTime, long poll, long maxTimeoutMillis, String timedOutMessage) throws TimeoutException { long now = SystemClock.elapsedRealtime(); long end = now + poll; // if we get InterruptedException's, ignore them and just keep sleeping while (now < end) { try { Thread.sleep(end - now); } catch (InterruptedException e) { // ignore interrupted exceptions } now = SystemClock.elapsedRealtime(); } currentTotalWaitTime += poll; if (currentTotalWaitTime > maxTimeoutMillis) { throw new TimeoutException(timedOutMessage); } return currentTotalWaitTime; } /** * Synchronously waits for external store to be mounted (eg: SD Card). * * @throws InterruptedException if interrupted * @throws Exception if timed out waiting for SD card to mount */ protected void waitForExternalStoreMount() throws Exception { String extStorageState = Environment.getExternalStorageState(); int currentWaitTime = 0; while (!extStorageState.equals(Environment.MEDIA_MOUNTED)) { Log.i(LOG_TAG, "Waiting for SD card..."); currentWaitTime = timeoutWait(currentWaitTime, DEFAULT_WAIT_POLL_TIME, DEFAULT_MAX_WAIT_TIME, "Timed out waiting for SD Card to be ready!"); extStorageState = Environment.getExternalStorageState(); } } /** * Synchronously waits for a download to start. * * @param dlRequest the download request id used by Download Manager to track the download. * @throws Exception if timed out while waiting for SD card to mount */ protected void waitForDownloadToStart(long dlRequest) throws Exception { Cursor cursor = getCursor(dlRequest); try { int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS); int value = cursor.getInt(columnIndex); int currentWaitTime = 0; while (value != DownloadManager.STATUS_RUNNING && (value != DownloadManager.STATUS_FAILED) && (value != DownloadManager.STATUS_SUCCESSFUL)) { Log.i(LOG_TAG, "Waiting for download to start..."); currentWaitTime = timeoutWait(currentWaitTime, WAIT_FOR_DOWNLOAD_POLL_TIME, MAX_WAIT_FOR_DOWNLOAD_TIME, "Timed out waiting for download to start!"); cursor.requery(); assertTrue(cursor.moveToFirst()); columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS); value = cursor.getInt(columnIndex); } assertFalse("Download failed immediately after start", value == DownloadManager.STATUS_FAILED); } finally { cursor.close(); } } /** * Synchronously waits for the download manager to start incrementing the number of * bytes downloaded so far. * * @param id DownloadManager download id that needs to be checked. * @param bytesToReceive how many bytes do we need to wait to receive. * @throws Exception if timed out while waiting for the file to grow in size. */ protected void waitToReceiveData(long id, long bytesToReceive) throws Exception { int currentWaitTime = 0; long expectedSize = getBytesDownloaded(id) + bytesToReceive; long currentSize = 0; while ((currentSize = getBytesDownloaded(id)) <= expectedSize) { Log.i(LOG_TAG, String.format("expect: %d, cur: %d. Waiting for file to be written to...", expectedSize, currentSize)); currentWaitTime = timeoutWait(currentWaitTime, WAIT_FOR_DOWNLOAD_POLL_TIME, MAX_WAIT_FOR_DOWNLOAD_TIME, "Timed out waiting for file to be written to."); } } private long getBytesDownloaded(long id) { DownloadManager.Query q = new DownloadManager.Query(); q.setFilterById(id); Cursor response = mDownloadManager.query(q); if (response.getCount() < 1) { Log.i(LOG_TAG, String.format("Query to download manager returned nothing for id %d",id)); response.close(); return -1; } while(response.moveToNext()) { int index = response.getColumnIndex(DownloadManager.COLUMN_ID); if (id == response.getLong(index)) { break; } } int index = response.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR); if (index < 0) { Log.i(LOG_TAG, String.format("No downloaded bytes for id %d", id)); response.close(); return -1; } long size = response.getLong(index); response.close(); return size; } /** * Helper to remove all downloads that are registered with the DL Manager. * * Note: This gives us a clean slate b/c it includes downloads that are pending, running, * paused, or have completed. */ protected void removeAllCurrentDownloads() { Log.i(LOG_TAG, "Removing all current registered downloads..."); Cursor cursor = mDownloadManager.query(new Query()); try { if (cursor.moveToFirst()) { do { int index = cursor.getColumnIndex(DownloadManager.COLUMN_ID); long downloadId = cursor.getLong(index); mDownloadManager.remove(downloadId); } while (cursor.moveToNext()); } } finally { cursor.close(); } } /** * Performs a query based on ID and returns a Cursor for the query. * * @param id The id of the download in DL Manager; pass -1 to query all downloads * @return A cursor for the query results */ protected Cursor getCursor(long id) throws Exception { Query query = new Query(); if (id != -1) { query.setFilterById(id); } Cursor cursor = mDownloadManager.query(query); int currentWaitTime = 0; try { while (!cursor.moveToFirst()) { Thread.sleep(DEFAULT_WAIT_POLL_TIME); currentWaitTime += DEFAULT_WAIT_POLL_TIME; if (currentWaitTime > DEFAULT_MAX_WAIT_TIME) { fail("timed out waiting for a non-null query result"); } cursor.requery(); } } catch (Exception e) { cursor.close(); throw e; } return cursor; } }