summaryrefslogtreecommitdiffstats
path: root/src/com/android/providers/downloads/DownloadThread.java
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2016-04-20 23:23:09 -0600
committerJeff Sharkey <jsharkey@android.com>2016-04-25 12:59:46 -0600
commit3a5f5eafb34eaa4963c801882148e8f61514a61b (patch)
treec38ae2f58cb39e4e17be37e8eec2fe040b4b6436 /src/com/android/providers/downloads/DownloadThread.java
parentdbcd4cfe7f0fed77a77afb1c1d242a508fc5462a (diff)
downloadandroid_packages_providers_DownloadProvider-3a5f5eafb34eaa4963c801882148e8f61514a61b.tar.gz
android_packages_providers_DownloadProvider-3a5f5eafb34eaa4963c801882148e8f61514a61b.tar.bz2
android_packages_providers_DownloadProvider-3a5f5eafb34eaa4963c801882148e8f61514a61b.zip
Move DownloadManager to use JobScheduler.
JobScheduler is in a much better position to coordinate tasks across the platform to optimize battery and RAM usage. This change removes a bunch of manual scheduling logic by representing each download as a separate job with relevant scheduling constraints. Requested network types, retry backoff timing, and newly added charging and idle constraints are plumbed through as job parameters. When a job times out, we halt the download and schedule it to resume later. The majority of downloads should have ETag values to enable resuming like this. Remove local wakelocks, since the platform now acquires and blames our jobs on the requesting app. When an active download is pushing updates to the database, check for both paused and cancelled state to quickly halt an ongoing download. Shift DownloadNotifier to update directly based on a Cursor, since we no longer have the overhead of fully-parsed DownloadInfo objects. Unify a handful of worker threads into a single shared thread. Remove legacy "large download" activity that was thrown in the face of the user; the UX best-practice is to go through notification, and update that dialog to let the user override and continue if under the hard limit. Bug: 28098882, 26571724 Change-Id: I33ebe59b3c2ea9c89ec526f70b1950c734abc4a7
Diffstat (limited to 'src/com/android/providers/downloads/DownloadThread.java')
-rw-r--r--src/com/android/providers/downloads/DownloadThread.java182
1 files changed, 96 insertions, 86 deletions
diff --git a/src/com/android/providers/downloads/DownloadThread.java b/src/com/android/providers/downloads/DownloadThread.java
index 9de563fb..c559367f 100644
--- a/src/com/android/providers/downloads/DownloadThread.java
+++ b/src/com/android/providers/downloads/DownloadThread.java
@@ -16,11 +16,18 @@
package com.android.providers.downloads;
+import static android.provider.Downloads.Impl.COLUMN_CONTROL;
+import static android.provider.Downloads.Impl.COLUMN_DELETED;
+import static android.provider.Downloads.Impl.COLUMN_STATUS;
+import static android.provider.Downloads.Impl.CONTROL_PAUSED;
import static android.provider.Downloads.Impl.STATUS_BAD_REQUEST;
import static android.provider.Downloads.Impl.STATUS_CANCELED;
import static android.provider.Downloads.Impl.STATUS_CANNOT_RESUME;
import static android.provider.Downloads.Impl.STATUS_FILE_ERROR;
import static android.provider.Downloads.Impl.STATUS_HTTP_DATA_ERROR;
+import static android.provider.Downloads.Impl.STATUS_PAUSED_BY_APP;
+import static android.provider.Downloads.Impl.STATUS_QUEUED_FOR_WIFI;
+import static android.provider.Downloads.Impl.STATUS_RUNNING;
import static android.provider.Downloads.Impl.STATUS_SUCCESS;
import static android.provider.Downloads.Impl.STATUS_TOO_MANY_REDIRECTS;
import static android.provider.Downloads.Impl.STATUS_UNHANDLED_HTTP_CODE;
@@ -28,7 +35,9 @@ import static android.provider.Downloads.Impl.STATUS_UNKNOWN_ERROR;
import static android.provider.Downloads.Impl.STATUS_WAITING_FOR_NETWORK;
import static android.provider.Downloads.Impl.STATUS_WAITING_TO_RETRY;
import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+
import static com.android.providers.downloads.Constants.TAG;
+
import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
import static java.net.HttpURLConnection.HTTP_MOVED_PERM;
import static java.net.HttpURLConnection.HTTP_MOVED_TEMP;
@@ -38,6 +47,8 @@ import static java.net.HttpURLConnection.HTTP_PRECON_FAILED;
import static java.net.HttpURLConnection.HTTP_SEE_OTHER;
import static java.net.HttpURLConnection.HTTP_UNAVAILABLE;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
@@ -51,19 +62,16 @@ import android.net.NetworkPolicyManager;
import android.net.TrafficStats;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
-import android.os.PowerManager;
import android.os.Process;
import android.os.SystemClock;
-import android.os.WorkSource;
import android.provider.Downloads;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.util.Log;
+import android.util.MathUtils;
import android.util.Pair;
-import com.android.providers.downloads.DownloadInfo.NetworkState;
-
import libcore.io.IoUtils;
import java.io.File;
@@ -89,7 +97,7 @@ import java.net.URLConnection;
* Failed network requests are retried several times before giving up. Local
* disk errors fail immediately and are not retried.
*/
-public class DownloadThread implements Runnable {
+public class DownloadThread extends Thread {
// TODO: bind each download to a specific network interface to avoid state
// checking races once we have ConnectivityManager API
@@ -104,6 +112,10 @@ public class DownloadThread implements Runnable {
private final Context mContext;
private final SystemFacade mSystemFacade;
private final DownloadNotifier mNotifier;
+ private final NetworkPolicyManager mNetworkPolicy;
+
+ private final DownloadJobService mJobService;
+ private final JobParameters mParams;
private final long mId;
@@ -134,6 +146,14 @@ public class DownloadThread implements Runnable {
public String mErrorMsg;
+ private static final String NOT_CANCELED = COLUMN_STATUS + " != '" + STATUS_CANCELED + "'";
+ private static final String NOT_DELETED = COLUMN_DELETED + " == '0'";
+ private static final String NOT_PAUSED = "(" + COLUMN_CONTROL + " IS NULL OR "
+ + COLUMN_CONTROL + " != '" + CONTROL_PAUSED + "')";
+
+ private static final String SELECTION_VALID = NOT_CANCELED + " AND " + NOT_DELETED + " AND "
+ + NOT_PAUSED;
+
public DownloadInfoDelta(DownloadInfo info) {
mUri = info.mUri;
mFileName = info.mFileName;
@@ -179,8 +199,12 @@ public class DownloadThread implements Runnable {
*/
public void writeToDatabaseOrThrow() throws StopRequestException {
if (mContext.getContentResolver().update(mInfo.getAllDownloadsUri(),
- buildContentValues(), Downloads.Impl.COLUMN_DELETED + " == '0'", null) == 0) {
- throw new StopRequestException(STATUS_CANCELED, "Download deleted or missing!");
+ buildContentValues(), SELECTION_VALID, null) == 0) {
+ if (mInfo.queryDownloadControl() == CONTROL_PAUSED) {
+ throw new StopRequestException(STATUS_PAUSED_BY_APP, "Download paused!");
+ } else {
+ throw new StopRequestException(STATUS_CANCELED, "Download deleted or missing!");
+ }
}
}
}
@@ -197,6 +221,8 @@ public class DownloadThread implements Runnable {
private long mLastUpdateBytes = 0;
private long mLastUpdateTime = 0;
+ private Network mNetwork;
+
private int mNetworkType = ConnectivityManager.TYPE_NONE;
/** Historical bytes/second speed of this download. */
@@ -206,11 +232,17 @@ public class DownloadThread implements Runnable {
/** Bytes transferred since current sample started. */
private long mSpeedSampleBytes;
- public DownloadThread(Context context, SystemFacade systemFacade, DownloadNotifier notifier,
- DownloadInfo info) {
- mContext = context;
- mSystemFacade = systemFacade;
- mNotifier = notifier;
+ /** Flag indicating that thread must be halted */
+ private volatile boolean mShutdownRequested;
+
+ public DownloadThread(DownloadJobService service, JobParameters params, DownloadInfo info) {
+ mContext = service;
+ mSystemFacade = Helpers.getSystemFacade(mContext);
+ mNotifier = Helpers.getDownloadNotifier(mContext);
+ mNetworkPolicy = mContext.getSystemService(NetworkPolicyManager.class);
+
+ mJobService = service;
+ mParams = params;
mId = info.mId;
mInfo = info;
@@ -223,29 +255,32 @@ public class DownloadThread implements Runnable {
// Skip when download already marked as finished; this download was
// probably started again while racing with UpdateThread.
- if (DownloadInfo.queryDownloadStatus(mContext.getContentResolver(), mId)
- == Downloads.Impl.STATUS_SUCCESS) {
+ if (mInfo.queryDownloadStatus() == Downloads.Impl.STATUS_SUCCESS) {
logDebug("Already finished; skipping");
return;
}
- final NetworkPolicyManager netPolicy = NetworkPolicyManager.from(mContext);
- PowerManager.WakeLock wakeLock = null;
- final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-
try {
- wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG);
- wakeLock.setWorkSource(new WorkSource(mInfo.mUid));
- wakeLock.acquire();
-
// while performing download, register for rules updates
- netPolicy.registerListener(mPolicyListener);
+ mNetworkPolicy.registerListener(mPolicyListener);
logDebug("Starting");
+ mInfoDelta.mStatus = STATUS_RUNNING;
+ mInfoDelta.writeToDatabase();
+
+ // Use the caller's default network to make this connection, since
+ // they might be subject to restrictions that we shouldn't let them
+ // circumvent.
+ mNetwork = mSystemFacade.getActiveNetwork(mInfo.mUid);
+ if (mNetwork == null) {
+ throw new StopRequestException(STATUS_WAITING_FOR_NETWORK,
+ "No network associated with requesting UID");
+ }
+
// Remember which network this download started on; used to
// determine if errors were due to network changes.
- final NetworkInfo info = mSystemFacade.getActiveNetworkInfo(mInfo.mUid);
+ final NetworkInfo info = mSystemFacade.getNetworkInfo(mNetwork);
if (info != null) {
mNetworkType = info.getType();
}
@@ -288,7 +323,7 @@ public class DownloadThread implements Runnable {
}
if (mInfoDelta.mNumFailed < Constants.MAX_RETRIES) {
- final NetworkInfo info = mSystemFacade.getActiveNetworkInfo(mInfo.mUid);
+ final NetworkInfo info = mSystemFacade.getNetworkInfo(mNetwork);
if (info != null && info.getType() == mNetworkType && info.isConnected()) {
// Underlying network is still intact, use normal backoff
mInfoDelta.mStatus = STATUS_WAITING_TO_RETRY;
@@ -321,20 +356,27 @@ public class DownloadThread implements Runnable {
mInfoDelta.writeToDatabase();
- if (Downloads.Impl.isStatusCompleted(mInfoDelta.mStatus)) {
- mInfo.sendIntentIfRequested();
- }
-
TrafficStats.clearThreadStatsTag();
TrafficStats.clearThreadStatsUid();
- netPolicy.unregisterListener(mPolicyListener);
+ mNetworkPolicy.unregisterListener(mPolicyListener);
+ }
- if (wakeLock != null) {
- wakeLock.release();
- wakeLock = null;
+ if (Downloads.Impl.isStatusCompleted(mInfoDelta.mStatus)) {
+ mInfo.sendIntentIfRequested();
+ if (mInfo.shouldScanFile()) {
+ DownloadScanner.requestScanBlocking(mContext, mInfo);
}
+ } else if (mInfoDelta.mStatus == STATUS_WAITING_TO_RETRY
+ || mInfoDelta.mStatus == STATUS_WAITING_FOR_NETWORK) {
+ Helpers.scheduleJob(mContext, DownloadInfo.queryDownloadInfo(mContext, mId));
}
+
+ mJobService.jobFinishedInternal(mParams, false);
+ }
+
+ public void requestShutdown() {
+ mShutdownRequested = true;
}
/**
@@ -352,15 +394,6 @@ public class DownloadThread implements Runnable {
throw new StopRequestException(STATUS_BAD_REQUEST, e);
}
- // Use the caller's default network to make this connection, since they might be subject to
- // restrictions that we shouldn't let them circumvent.
- final Network network = mSystemFacade.getActiveNetwork(mInfo.mUid);
- if (network == null) {
- throw new StopRequestException(Downloads.Impl.STATUS_WAITING_FOR_NETWORK,
- "no network associated with requesting UID");
- }
- logDebug("Using network: " + network);
-
boolean cleartextTrafficPermitted = mSystemFacade.isCleartextTrafficPermitted(mInfo.mUid);
int redirectionCount = 0;
while (redirectionCount++ < Constants.MAX_REDIRECTS) {
@@ -379,7 +412,7 @@ public class DownloadThread implements Runnable {
// Check that the caller is allowed to make network connections. If so, make one on
// their behalf to open the url.
checkConnectivity();
- conn = (HttpURLConnection) network.openConnection(url);
+ conn = (HttpURLConnection) mNetwork.openConnection(url);
conn.setInstanceFollowRedirects(false);
conn.setConnectTimeout(DEFAULT_TIMEOUT);
conn.setReadTimeout(DEFAULT_TIMEOUT);
@@ -542,7 +575,7 @@ public class DownloadThread implements Runnable {
} finally {
if (drmClient != null) {
- drmClient.release();
+ drmClient.close();
}
IoUtils.closeQuietly(in);
@@ -565,7 +598,12 @@ public class DownloadThread implements Runnable {
throws StopRequestException {
final byte buffer[] = new byte[Constants.BUFFER_SIZE];
while (true) {
- checkPausedOrCanceled();
+ if (mPolicyDirty) checkConnectivity();
+
+ if (mShutdownRequested) {
+ throw new StopRequestException(STATUS_HTTP_DATA_ERROR,
+ "Local halt requested; job probably timed out");
+ }
int len = -1;
try {
@@ -665,38 +703,19 @@ public class DownloadThread implements Runnable {
// checking connectivity will apply current policy
mPolicyDirty = false;
- final NetworkState networkUsable = mInfo.checkCanUseNetwork(mInfoDelta.mTotalBytes);
- if (networkUsable != NetworkState.OK) {
- int status = Downloads.Impl.STATUS_WAITING_FOR_NETWORK;
- if (networkUsable == NetworkState.UNUSABLE_DUE_TO_SIZE) {
- status = Downloads.Impl.STATUS_QUEUED_FOR_WIFI;
- mInfo.notifyPauseDueToSize(true);
- } else if (networkUsable == NetworkState.RECOMMENDED_UNUSABLE_DUE_TO_SIZE) {
- status = Downloads.Impl.STATUS_QUEUED_FOR_WIFI;
- mInfo.notifyPauseDueToSize(false);
- }
- throw new StopRequestException(status, networkUsable.name());
- }
- }
+ final boolean allowMetered = mInfo
+ .getRequiredNetworkType(mInfoDelta.mTotalBytes) != JobInfo.NETWORK_TYPE_UNMETERED;
+ final boolean allowRoaming = mInfo.isRoamingAllowed();
- /**
- * Check if the download has been paused or canceled, stopping the request
- * appropriately if it has been.
- */
- private void checkPausedOrCanceled() throws StopRequestException {
- synchronized (mInfo) {
- if (mInfo.mControl == Downloads.Impl.CONTROL_PAUSED) {
- throw new StopRequestException(
- Downloads.Impl.STATUS_PAUSED_BY_APP, "download paused by owner");
- }
- if (mInfo.mStatus == Downloads.Impl.STATUS_CANCELED || mInfo.mDeleted) {
- throw new StopRequestException(Downloads.Impl.STATUS_CANCELED, "download canceled");
- }
+ final NetworkInfo info = mSystemFacade.getNetworkInfo(mNetwork);
+ if (info == null || !info.isConnected()) {
+ throw new StopRequestException(STATUS_WAITING_FOR_NETWORK, "Network is disconnected");
}
-
- // if policy has been changed, trigger connectivity check
- if (mPolicyDirty) {
- checkConnectivity();
+ if (info.isRoaming() && !allowRoaming) {
+ throw new StopRequestException(STATUS_WAITING_FOR_NETWORK, "Network is roaming");
+ }
+ if (info.isMetered() && !allowMetered) {
+ throw new StopRequestException(STATUS_QUEUED_FOR_WIFI, "Network is metered");
}
}
@@ -781,17 +800,8 @@ public class DownloadThread implements Runnable {
private void parseUnavailableHeaders(HttpURLConnection conn) {
long retryAfter = conn.getHeaderFieldInt("Retry-After", -1);
- if (retryAfter < 0) {
- retryAfter = 0;
- } else {
- if (retryAfter < Constants.MIN_RETRY_AFTER) {
- retryAfter = Constants.MIN_RETRY_AFTER;
- } else if (retryAfter > Constants.MAX_RETRY_AFTER) {
- retryAfter = Constants.MAX_RETRY_AFTER;
- }
- retryAfter += Helpers.sRandom.nextInt(Constants.MIN_RETRY_AFTER + 1);
- }
-
+ retryAfter = MathUtils.constrain(retryAfter, Constants.MIN_RETRY_AFTER,
+ Constants.MAX_RETRY_AFTER);
mInfoDelta.mRetryAfter = (int) (retryAfter * SECOND_IN_MILLIS);
}