summaryrefslogtreecommitdiffstats
path: root/src/com/android/providers/downloads/DownloadThread.java
diff options
context:
space:
mode:
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);
}