diff options
Diffstat (limited to 'src/com/android/providers/downloads/DownloadThread.java')
-rw-r--r-- | src/com/android/providers/downloads/DownloadThread.java | 182 |
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); } |