merge in jb-mr2-release history after reset to master
diff --git a/src/com/android/providers/downloads/DownloadHandler.java b/src/com/android/providers/downloads/DownloadHandler.java
deleted file mode 100644
index c376ff1..0000000
--- a/src/com/android/providers/downloads/DownloadHandler.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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 android.util.LongSparseArray;
-
-import com.android.internal.annotations.GuardedBy;
-
-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";
-
- @GuardedBy("this")
- private final LinkedHashMap<Long, DownloadInfo> mDownloadsQueue =
- new LinkedHashMap<Long, DownloadInfo>();
- @GuardedBy("this")
- private final HashMap<Long, DownloadInfo> mDownloadsInProgress =
- new HashMap<Long, DownloadInfo>();
- @GuardedBy("this")
- private final LongSparseArray<Long> mCurrentSpeed = new LongSparseArray<Long>();
-
- private final int mMaxConcurrentDownloadsAllowed = Resources.getSystem().getInteger(
- com.android.internal.R.integer.config_MaxConcurrentDownloadsAllowed);
-
- private static final DownloadHandler sDownloadHandler = new DownloadHandler();
-
- public static DownloadHandler getInstance() {
- return sDownloadHandler;
- }
-
- public 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);
- startDownloadThreadLocked();
- }
- }
-
- private void startDownloadThreadLocked() {
- 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);
- }
- }
-
- public synchronized boolean hasDownloadInQueue(long id) {
- return mDownloadsQueue.containsKey(id) || mDownloadsInProgress.containsKey(id);
- }
-
- public synchronized void dequeueDownload(long id) {
- mDownloadsInProgress.remove(id);
- mCurrentSpeed.remove(id);
- startDownloadThreadLocked();
- if (mDownloadsInProgress.size() == 0 && mDownloadsQueue.size() == 0) {
- notifyAll();
- }
- }
-
- public synchronized void setCurrentSpeed(long id, long speed) {
- mCurrentSpeed.put(id, speed);
- }
-
- public synchronized long getCurrentSpeed(long id) {
- return mCurrentSpeed.get(id, -1L);
- }
-}
diff --git a/src/com/android/providers/downloads/DownloadInfo.java b/src/com/android/providers/downloads/DownloadInfo.java
index b984bde..74b52d4 100644
--- a/src/com/android/providers/downloads/DownloadInfo.java
+++ b/src/com/android/providers/downloads/DownloadInfo.java
@@ -31,15 +31,18 @@
import android.provider.Downloads;
import android.provider.Downloads.Impl;
import android.text.TextUtils;
-import android.util.Log;
import android.util.Pair;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IndentingPrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
/**
* Stores information about an individual download.
@@ -58,8 +61,9 @@
}
public DownloadInfo newDownloadInfo(Context context, SystemFacade systemFacade,
- StorageManager storageManager) {
- DownloadInfo info = new DownloadInfo(context, systemFacade, storageManager);
+ StorageManager storageManager, DownloadNotifier notifier) {
+ final DownloadInfo info = new DownloadInfo(
+ context, systemFacade, storageManager, notifier);
updateFromDatabase(info);
readRequestHeaders(info);
return info;
@@ -200,7 +204,6 @@
*/
public static final String EXTRA_IS_WIFI_REQUIRED = "isWifiRequired";
-
public long mId;
public String mUri;
public boolean mNoIntegrity;
@@ -239,14 +242,24 @@
private List<Pair<String, String>> mRequestHeaders = new ArrayList<Pair<String, String>>();
+ /**
+ * Result of last {@link DownloadThread} started by
+ * {@link #startIfReady(ExecutorService)}.
+ */
+ @GuardedBy("this")
+ private Future<?> mActiveTask;
+
private final Context mContext;
private final SystemFacade mSystemFacade;
private final StorageManager mStorageManager;
+ private final DownloadNotifier mNotifier;
- private DownloadInfo(Context context, SystemFacade systemFacade, StorageManager storageManager) {
+ private DownloadInfo(Context context, SystemFacade systemFacade, StorageManager storageManager,
+ DownloadNotifier notifier) {
mContext = context;
mSystemFacade = systemFacade;
mStorageManager = storageManager;
+ mNotifier = notifier;
mFuzz = Helpers.sRandom.nextInt(1001);
}
@@ -297,14 +310,9 @@
}
/**
- * Returns whether this download (which the download manager hasn't seen yet)
- * should be started.
+ * Returns whether this download should be enqueued.
*/
- private boolean isReadyToStart(long now) {
- if (DownloadHandler.getInstance().hasDownloadInQueue(mId)) {
- // already running
- return false;
- }
+ private boolean isReadyToStart() {
if (mControl == Downloads.Impl.CONTROL_PAUSED) {
// the download is paused, so it's not going to start
return false;
@@ -322,6 +330,7 @@
case Downloads.Impl.STATUS_WAITING_TO_RETRY:
// download was waiting for a delayed restart
+ final long now = mSystemFacade.currentTimeMillis();
return restartTime(now) <= now;
case Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR:
// is the media mounted?
@@ -437,21 +446,27 @@
return NetworkState.OK;
}
- void startIfReady(long now, StorageManager storageManager) {
- if (!isReadyToStart(now)) {
- return;
- }
+ /**
+ * If download is ready to start, and isn't already pending or executing,
+ * create a {@link DownloadThread} and enqueue it into given
+ * {@link Executor}.
+ */
+ public void startIfReady(ExecutorService executor) {
+ synchronized (this) {
+ final boolean isActive = mActiveTask != null && !mActiveTask.isDone();
+ if (isReadyToStart() && !isActive) {
+ 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);
+ }
- if (Constants.LOGV) {
- Log.v(Constants.TAG, "Service spawning thread to handle download " + mId);
+ final DownloadThread task = new DownloadThread(
+ mContext, mSystemFacade, this, mStorageManager, mNotifier);
+ mActiveTask = executor.submit(task);
+ }
}
- 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);
- }
- DownloadHandler.getInstance().enqueueDownload(this);
}
public boolean isOnCache() {
@@ -553,11 +568,6 @@
mContext.startActivity(intent);
}
- void startDownloadThread() {
- // TODO: keep this thread strongly referenced
- new DownloadThread(mContext, mSystemFacade, this, mStorageManager).start();
- }
-
/**
* Query and return status of requested download.
*/
diff --git a/src/com/android/providers/downloads/DownloadNotifier.java b/src/com/android/providers/downloads/DownloadNotifier.java
index daae783..2dd9805 100644
--- a/src/com/android/providers/downloads/DownloadNotifier.java
+++ b/src/com/android/providers/downloads/DownloadNotifier.java
@@ -32,6 +32,7 @@
import android.provider.Downloads;
import android.text.TextUtils;
import android.text.format.DateUtils;
+import android.util.LongSparseLongArray;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Maps;
@@ -66,6 +67,13 @@
@GuardedBy("mActiveNotifs")
private final HashMap<String, Long> mActiveNotifs = Maps.newHashMap();
+ /**
+ * Current speed of active downloads, mapped from {@link DownloadInfo#mId}
+ * to speed in bytes per second.
+ */
+ @GuardedBy("mDownloadSpeed")
+ private final LongSparseLongArray mDownloadSpeed = new LongSparseLongArray();
+
public DownloadNotifier(Context context) {
mContext = context;
mNotifManager = (NotificationManager) context.getSystemService(
@@ -77,6 +85,20 @@
}
/**
+ * Notify the current speed of an active download, used for calcuating
+ * estimated remaining time.
+ */
+ public void notifyDownloadSpeed(long id, long bytesPerSecond) {
+ synchronized (mDownloadSpeed) {
+ if (bytesPerSecond != 0) {
+ mDownloadSpeed.put(id, bytesPerSecond);
+ } else {
+ mDownloadSpeed.delete(id);
+ }
+ }
+ }
+
+ /**
* Update {@link NotificationManager} to reflect the given set of
* {@link DownloadInfo}, adding, collapsing, and removing as needed.
*/
@@ -163,16 +185,16 @@
String remainingText = null;
String percentText = null;
if (type == TYPE_ACTIVE) {
- final DownloadHandler handler = DownloadHandler.getInstance();
-
long current = 0;
long total = 0;
long speed = 0;
- for (DownloadInfo info : cluster) {
- if (info.mTotalBytes != -1) {
- current += info.mCurrentBytes;
- total += info.mTotalBytes;
- speed += handler.getCurrentSpeed(info.mId);
+ synchronized (mDownloadSpeed) {
+ for (DownloadInfo info : cluster) {
+ if (info.mTotalBytes != -1) {
+ current += info.mCurrentBytes;
+ total += info.mTotalBytes;
+ speed += mDownloadSpeed.get(info.mId);
+ }
}
}
diff --git a/src/com/android/providers/downloads/DownloadService.java b/src/com/android/providers/downloads/DownloadService.java
index 4a1b40d..039f12c 100644
--- a/src/com/android/providers/downloads/DownloadService.java
+++ b/src/com/android/providers/downloads/DownloadService.java
@@ -26,6 +26,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.Cursor;
import android.media.IMediaScannerListener;
@@ -53,6 +54,10 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
/**
* Performs the background downloads requested by applications that use the Downloads provider.
@@ -76,12 +81,26 @@
@GuardedBy("mDownloads")
private Map<Long, DownloadInfo> mDownloads = Maps.newHashMap();
+ private final ExecutorService mExecutor = buildDownloadExecutor();
+
+ private static ExecutorService buildDownloadExecutor() {
+ final int maxConcurrent = Resources.getSystem().getInteger(
+ com.android.internal.R.integer.config_MaxConcurrentDownloadsAllowed);
+
+ // Create a bounded thread pool for executing downloads; it creates
+ // threads as needed (up to maximum) and reclaims them when finished.
+ final ThreadPoolExecutor executor = new ThreadPoolExecutor(
+ maxConcurrent, maxConcurrent, 10, TimeUnit.SECONDS,
+ new LinkedBlockingQueue<Runnable>());
+ executor.allowCoreThreadTimeOut(true);
+ return executor;
+ }
+
/**
* The thread that updates the internal download list from the content
* provider.
*/
- @VisibleForTesting
- UpdateThread mUpdateThread;
+ private UpdateThread mUpdateThread;
/**
* Whether the internal download list should be updated from the content
@@ -435,14 +454,15 @@
* download if appropriate.
*/
private DownloadInfo insertDownloadLocked(DownloadInfo.Reader reader, long now) {
- DownloadInfo info = reader.newDownloadInfo(this, mSystemFacade, mStorageManager);
+ final DownloadInfo info = reader.newDownloadInfo(
+ this, mSystemFacade, mStorageManager, mNotifier);
mDownloads.put(info.mId, info);
if (Constants.LOGVV) {
Log.v(Constants.TAG, "processing inserted download " + info.mId);
}
- info.startIfReady(now, mStorageManager);
+ info.startIfReady(mExecutor);
return info;
}
@@ -458,7 +478,7 @@
Log.v(Constants.TAG, "processing updated download " + info.mId +
", status: " + info.mStatus);
}
- info.startIfReady(now, mStorageManager);
+ info.startIfReady(mExecutor);
}
/**
diff --git a/src/com/android/providers/downloads/DownloadThread.java b/src/com/android/providers/downloads/DownloadThread.java
index 5bb1e9b..bd347e4 100644
--- a/src/com/android/providers/downloads/DownloadThread.java
+++ b/src/com/android/providers/downloads/DownloadThread.java
@@ -68,10 +68,10 @@
import libcore.net.http.HttpEngine;
/**
- * Thread which executes a given {@link DownloadInfo}: making network requests,
+ * Task which executes a given {@link DownloadInfo}: making network requests,
* persisting data to disk, and updating {@link DownloadProvider}.
*/
-public class DownloadThread extends Thread {
+public class DownloadThread implements Runnable {
private static final int HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
private static final int HTTP_TEMP_REDIRECT = 307;
@@ -82,15 +82,17 @@
private final DownloadInfo mInfo;
private final SystemFacade mSystemFacade;
private final StorageManager mStorageManager;
+ private final DownloadNotifier mNotifier;
private volatile boolean mPolicyDirty;
public DownloadThread(Context context, SystemFacade systemFacade, DownloadInfo info,
- StorageManager storageManager) {
+ StorageManager storageManager, DownloadNotifier notifier) {
mContext = context;
mSystemFacade = systemFacade;
mInfo = info;
mStorageManager = storageManager;
+ mNotifier = notifier;
}
/**
@@ -151,16 +153,13 @@
}
}
- /**
- * Executes the download in a separate thread
- */
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
try {
runInternal();
} finally {
- DownloadHandler.getInstance().dequeueDownload(mInfo.mId);
+ mNotifier.notifyDownloadSpeed(mInfo.mId, 0);
}
}
@@ -526,7 +525,7 @@
state.mSpeedSampleStart = now;
state.mSpeedSampleBytes = state.mCurrentBytes;
- DownloadHandler.getInstance().setCurrentSpeed(mInfo.mId, state.mSpeed);
+ mNotifier.notifyDownloadSpeed(mInfo.mId, state.mSpeed);
}
if (state.mCurrentBytes - state.mBytesNotified > Constants.MIN_PROGRESS_STEP &&