(master) bug:3204324 allow no more than certain number of concurrent downloads
Vasu Nori [Thu, 10 Mar 2011 19:57:56 +0000 (11:57 -0800)]
Change-Id: Ibbce0782fcf7649209d6f56be240209cebd9045b

src/com/android/providers/downloads/DownloadHandler.java [new file with mode: 0644]
src/com/android/providers/downloads/DownloadInfo.java
src/com/android/providers/downloads/DownloadService.java
src/com/android/providers/downloads/DownloadThread.java
tests/src/com/android/providers/downloads/AbstractDownloadManagerFunctionalTest.java
tests/src/com/android/providers/downloads/DownloadManagerFunctionalTest.java

diff --git a/src/com/android/providers/downloads/DownloadHandler.java b/src/com/android/providers/downloads/DownloadHandler.java
new file mode 100644 (file)
index 0000000..29d3470
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2011 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.providers.downloads;
+
+import android.content.res.Resources;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+
+public class DownloadHandler {
+
+    private static final String TAG = "DownloadHandler";
+    private final LinkedHashMap<Long, DownloadInfo> mDownloadsQueue =
+            new LinkedHashMap<Long, DownloadInfo>();
+    private final HashMap<Long, DownloadInfo> mDownloadsInProgress =
+            new HashMap<Long, DownloadInfo>();
+    private static final DownloadHandler mDownloadHandler = new DownloadHandler();
+    private final int mMaxConcurrentDownloadsAllowed = Resources.getSystem().getInteger(
+            com.android.internal.R.integer.config_MaxConcurrentDownloadsAllowed);
+
+    static DownloadHandler getInstance() {
+        return mDownloadHandler;
+    }
+
+    synchronized void enqueueDownload(DownloadInfo info) {
+        if (!mDownloadsQueue.containsKey(info.mId)) {
+            if (Constants.LOGV) {
+                Log.i(TAG, "enqueued download. id: " + info.mId + ", uri: " + info.mUri);
+            }
+            mDownloadsQueue.put(info.mId, info);
+            startDownloadThread();
+        }
+    }
+
+    private synchronized void startDownloadThread() {
+        Iterator<Long> keys = mDownloadsQueue.keySet().iterator();
+        ArrayList<Long> ids = new ArrayList<Long>();
+        while (mDownloadsInProgress.size() < mMaxConcurrentDownloadsAllowed && keys.hasNext()) {
+            Long id = keys.next();
+            DownloadInfo info = mDownloadsQueue.get(id);
+            info.startDownloadThread();
+            ids.add(id);
+            mDownloadsInProgress.put(id, mDownloadsQueue.get(id));
+            if (Constants.LOGV) {
+                Log.i(TAG, "started download for : " + id);
+            }
+        }
+        for (Long id : ids) {
+            mDownloadsQueue.remove(id);
+        }
+    }
+
+    synchronized boolean hasDownloadInQueue(long id) {
+        return mDownloadsQueue.containsKey(id) || mDownloadsInProgress.containsKey(id);
+    }
+
+    synchronized void dequeueDownload(long mId) {
+        mDownloadsInProgress.remove(mId);
+        startDownloadThread();
+        if (mDownloadsInProgress.size() == 0 && mDownloadsQueue.size() == 0) {
+            notifyAll();
+        }
+    }
+
+    // right now this is only used by tests. but there is no reason why it can't be used
+    // by any module using DownloadManager (TODO add API to DownloadManager.java)
+    public synchronized void WaitUntilDownloadsTerminate() throws InterruptedException {
+        if (mDownloadsInProgress.size() == 0 && mDownloadsQueue.size() == 0) {
+            if (Constants.LOGVV) {
+                Log.i(TAG, "nothing to wait on");
+            }
+            return;
+        }
+        if (Constants.LOGVV) {
+            for (DownloadInfo info : mDownloadsInProgress.values()) {
+                Log.i(TAG, "** progress: " + info.mId + ", " + info.mUri);
+            }
+            for (DownloadInfo info : mDownloadsQueue.values()) {
+                Log.i(TAG, "** in Q: " + info.mId + ", " + info.mUri);
+            }
+        }
+        if (Constants.LOGVV) {
+            Log.i(TAG, "waiting for 5 sec");
+        }
+        // wait upto 5 sec
+        wait(5 * 1000);
+    }
+}
index 2973937..bd8df86 100644 (file)
@@ -216,8 +216,6 @@ public class DownloadInfo {
 
     public int mFuzz;
 
-    public volatile boolean mHasActiveThread;
-
     private List<Pair<String, String>> mRequestHeaders = new ArrayList<Pair<String, String>>();
     private SystemFacade mSystemFacade;
     private Context mContext;
@@ -279,7 +277,7 @@ public class DownloadInfo {
      * should be started.
      */
     private boolean isReadyToStart(long now) {
-        if (mHasActiveThread) {
+        if (DownloadHandler.getInstance().hasDownloadInQueue(mId)) {
             // already running
             return false;
         }
@@ -442,19 +440,13 @@ public class DownloadInfo {
         if (Constants.LOGV) {
             Log.v(Constants.TAG, "Service spawning thread to handle download " + mId);
         }
-        if (mHasActiveThread) {
-            throw new IllegalStateException("Multiple threads on same download");
-        }
         if (mStatus != Impl.STATUS_RUNNING) {
             mStatus = Impl.STATUS_RUNNING;
             ContentValues values = new ContentValues();
             values.put(Impl.COLUMN_STATUS, mStatus);
             mContext.getContentResolver().update(getAllDownloadsUri(), values, null, null);
         }
-        DownloadThread downloader = new DownloadThread(mContext, mSystemFacade, this,
-                storageManager);
-        mHasActiveThread = true;
-        mSystemFacade.startThread(downloader);
+        DownloadHandler.getInstance().enqueueDownload(this);
     }
 
     public boolean isOnCache() {
@@ -543,4 +535,10 @@ public class DownloadInfo {
         intent.putExtra(EXTRA_IS_WIFI_REQUIRED, isWifiRequired);
         mContext.startActivity(intent);
     }
+
+    void startDownloadThread() {
+        DownloadThread downloader = new DownloadThread(mContext, mSystemFacade, this,
+                StorageManager.getInstance(mContext));
+        mSystemFacade.startThread(downloader);
+    }
 }
index d4e50f2..035eaff 100644 (file)
@@ -23,7 +23,6 @@ import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.app.Service;
 import android.content.ComponentName;
-import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
index 100ebca..77f7fa1 100644 (file)
@@ -171,16 +171,18 @@ public class DownloadThread extends Thread {
             finalStatus = Downloads.Impl.STATUS_SUCCESS;
         } catch (StopRequestException error) {
             // remove the cause before printing, in case it contains PII
-            errorMsg = "Aborting request for download " + mInfo.mId + ": " + error.getMessage();
-            Log.w(Constants.TAG, errorMsg);
+            errorMsg = error.getMessage();
+            String msg = "Aborting request for download " + mInfo.mId + ": " + errorMsg;
+            Log.w(Constants.TAG, msg);
             if (Constants.LOGV) {
-                Log.w(Constants.TAG, errorMsg, error);
+                Log.w(Constants.TAG, msg, error);
             }
             finalStatus = error.mFinalStatus;
             // fall through to finally block
         } catch (Throwable ex) { //sometimes the socket code throws unchecked exceptions
-            errorMsg = "Exception for id " + mInfo.mId + ": " + ex.getMessage();
-            Log.w(Constants.TAG, errorMsg, ex);
+            errorMsg = ex.getMessage();
+            String msg = "Exception for id " + mInfo.mId + ": " + errorMsg;
+            Log.w(Constants.TAG, msg, ex);
             finalStatus = Downloads.Impl.STATUS_UNKNOWN_ERROR;
             // falls through to the code that reports an error
         } finally {
@@ -196,7 +198,7 @@ public class DownloadThread extends Thread {
             notifyDownloadCompleted(finalStatus, state.mCountRetry, state.mRetryAfter,
                                     state.mGotData, state.mFilename,
                                     state.mNewUri, state.mMimeType, errorMsg);
-            mInfo.mHasActiveThread = false;
+            DownloadHandler.getInstance().dequeueDownload(mInfo.mId);
         }
         mStorageManager.incrementNumDownloadsSoFar();
     }
index 5283d42..d2ecf3e 100644 (file)
@@ -155,13 +155,12 @@ public abstract class AbstractDownloadManagerFunctionalTest extends
         Context realContext = getContext();
         mTestContext = new TestContext(realContext);
         setupProviderAndResolver();
-        assert isDatabaseEmpty(); // ensure we're not messing with real data
 
         mTestContext.setResolver(mResolver);
         setContext(mTestContext);
         setupService();
         getService().mSystemFacade = mSystemFacade;
-
+        assertTrue(isDatabaseEmpty()); // ensure we're not messing with real data
         mServer = new MockWebServer();
         mServer.play();
     }
index c3ac890..e01b617 100644 (file)
@@ -23,6 +23,8 @@ import android.net.Uri;
 import android.os.Environment;
 import android.provider.Downloads;
 import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
 import tests.http.MockWebServer;
 import tests.http.RecordedRequest;
 
@@ -37,6 +39,8 @@ import java.net.MalformedURLException;
  */
 @LargeTest
 public class DownloadManagerFunctionalTest extends AbstractDownloadManagerFunctionalTest {
+    private static final String TAG = "DownloadManagerFunctionalTest";
+
     public DownloadManagerFunctionalTest() {
         super(new FakeSystemFacade());
     }
@@ -104,6 +108,17 @@ public class DownloadManagerFunctionalTest extends AbstractDownloadManagerFuncti
 
     private void runUntilStatus(Uri downloadUri, int status) throws Exception {
         runService();
+        boolean done = false;
+        while (!done) {
+            int rslt = getDownloadStatus(downloadUri);
+            if (rslt == Downloads.Impl.STATUS_RUNNING || rslt == Downloads.Impl.STATUS_PENDING) {
+                Log.i(TAG, "status is: " + rslt + ", for: " + downloadUri);
+                DownloadHandler.getInstance().WaitUntilDownloadsTerminate();
+                Thread.sleep(100);
+            } else {
+                done = true;
+            }
+        }
         assertEquals(status, getDownloadStatus(downloadUri));
     }