diff options
Diffstat (limited to 'src/com/android/providers/downloads/DownloadInfo.java')
-rw-r--r-- | src/com/android/providers/downloads/DownloadInfo.java | 396 |
1 files changed, 118 insertions, 278 deletions
diff --git a/src/com/android/providers/downloads/DownloadInfo.java b/src/com/android/providers/downloads/DownloadInfo.java index bee5c4a9..c571de4d 100644 --- a/src/com/android/providers/downloads/DownloadInfo.java +++ b/src/com/android/providers/downloads/DownloadInfo.java @@ -16,27 +16,26 @@ package com.android.providers.downloads; +import static android.provider.Downloads.Impl.VISIBILITY_VISIBLE; +import static android.provider.Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED; + import static com.android.providers.downloads.Constants.TAG; import android.app.DownloadManager; +import android.app.job.JobInfo; import android.content.ContentResolver; import android.content.ContentUris; -import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.database.Cursor; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.NetworkInfo.DetailedState; import android.net.Uri; import android.os.Environment; import android.provider.Downloads; -import android.provider.Downloads.Impl; import android.text.TextUtils; +import android.text.format.DateUtils; import android.util.Log; import android.util.Pair; -import com.android.internal.annotations.GuardedBy; import com.android.internal.util.IndentingPrintWriter; import java.io.CharArrayWriter; @@ -45,9 +44,6 @@ 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; /** * Details about a specific download. Fields should only be mutated by updating @@ -66,14 +62,6 @@ public class DownloadInfo { mCursor = cursor; } - public DownloadInfo newDownloadInfo( - Context context, SystemFacade systemFacade, DownloadNotifier notifier) { - final DownloadInfo info = new DownloadInfo(context, systemFacade, notifier); - updateFromDatabase(info); - readRequestHeaders(info); - return info; - } - public void updateFromDatabase(DownloadInfo info) { info.mId = getLong(Downloads.Impl._ID); info.mUri = getString(Downloads.Impl.COLUMN_URI); @@ -105,6 +93,7 @@ public class DownloadInfo { info.mAllowedNetworkTypes = getInt(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES); info.mAllowRoaming = getInt(Downloads.Impl.COLUMN_ALLOW_ROAMING) != 0; info.mAllowMetered = getInt(Downloads.Impl.COLUMN_ALLOW_METERED) != 0; + info.mFlags = getInt(Downloads.Impl.COLUMN_FLAGS); info.mTitle = getString(Downloads.Impl.COLUMN_TITLE); info.mDescription = getString(Downloads.Impl.COLUMN_DESCRIPTION); info.mBypassRecommendedSizeLimit = @@ -115,7 +104,7 @@ public class DownloadInfo { } } - private void readRequestHeaders(DownloadInfo info) { + public void readRequestHeaders(DownloadInfo info) { info.mRequestHeaders.clear(); Uri headerUri = Uri.withAppendedPath( info.getAllDownloadsUri(), Downloads.Impl.RequestHeaders.URI_SEGMENT); @@ -159,56 +148,6 @@ public class DownloadInfo { } } - /** - * Constants used to indicate network state for a specific download, after - * applying any requested constraints. - */ - public enum NetworkState { - /** - * The network is usable for the given download. - */ - OK, - - /** - * There is no network connectivity. - */ - NO_CONNECTION, - - /** - * The download exceeds the maximum size for this network. - */ - UNUSABLE_DUE_TO_SIZE, - - /** - * The download exceeds the recommended maximum size for this network, - * the user must confirm for this download to proceed without WiFi. - */ - RECOMMENDED_UNUSABLE_DUE_TO_SIZE, - - /** - * The current connection is roaming, and the download can't proceed - * over a roaming connection. - */ - CANNOT_USE_ROAMING, - - /** - * The app requesting the download specific that it can't use the - * current network connection. - */ - TYPE_DISALLOWED_BY_REQUESTOR, - - /** - * Current network is blocked for requesting application. - */ - BLOCKED; - } - - /** - * For intents used to notify the user that a download exceeds a size threshold, if this extra - * is true, WiFi is required for this download size; otherwise, it is only recommended. - */ - public static final String EXTRA_IS_WIFI_REQUIRED = "isWifiRequired"; - public long mId; public String mUri; @Deprecated @@ -240,33 +179,35 @@ public class DownloadInfo { public int mAllowedNetworkTypes; public boolean mAllowRoaming; public boolean mAllowMetered; + public int mFlags; public String mTitle; public String mDescription; public int mBypassRecommendedSizeLimit; - public int mFuzz; - private List<Pair<String, String>> mRequestHeaders = new ArrayList<Pair<String, String>>(); - /** - * Result of last {@link DownloadThread} started by - * {@link #startDownloadIfReady(ExecutorService)}. - */ - @GuardedBy("this") - private Future<?> mSubmittedTask; - - @GuardedBy("this") - private DownloadThread mTask; - private final Context mContext; private final SystemFacade mSystemFacade; - private final DownloadNotifier mNotifier; - private DownloadInfo(Context context, SystemFacade systemFacade, DownloadNotifier notifier) { + public DownloadInfo(Context context) { mContext = context; - mSystemFacade = systemFacade; - mNotifier = notifier; - mFuzz = Helpers.sRandom.nextInt(1001); + mSystemFacade = Helpers.getSystemFacade(context); + } + + public static DownloadInfo queryDownloadInfo(Context context, long downloadId) { + final ContentResolver resolver = context.getContentResolver(); + try (Cursor cursor = resolver.query( + ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, downloadId), + null, null, null, null)) { + final DownloadInfo.Reader reader = new DownloadInfo.Reader(resolver, cursor); + final DownloadInfo info = new DownloadInfo(context); + if (cursor.moveToFirst()) { + reader.updateFromDatabase(info); + reader.readRequestHeaders(info); + return info; + } + } + return null; } public Collection<Pair<String, String>> getHeaders() { @@ -309,43 +250,93 @@ public class DownloadInfo { } /** - * Returns the time when a download should be restarted. + * Return if this download is visible to the user while running. + */ + public boolean isVisible() { + switch (mVisibility) { + case VISIBILITY_VISIBLE: + case VISIBILITY_VISIBLE_NOTIFY_COMPLETED: + return true; + default: + return false; + } + } + + /** + * Add random fuzz to the given delay so it's anywhere between 1-1.5x the + * requested delay. + */ + private long fuzzDelay(long delay) { + return delay + Helpers.sRandom.nextInt((int) (delay / 2)); + } + + /** + * Return minimum latency in milliseconds required before this download is + * allowed to start again. + * + * @see android.app.job.JobInfo.Builder#setMinimumLatency(long) */ - public long restartTime(long now) { - if (mNumFailed == 0) { - return now; + public long getMinimumLatency() { + if (mStatus == Downloads.Impl.STATUS_WAITING_TO_RETRY) { + final long now = mSystemFacade.currentTimeMillis(); + final long startAfter; + if (mNumFailed == 0) { + startAfter = now; + } else if (mRetryAfter > 0) { + startAfter = mLastMod + fuzzDelay(mRetryAfter); + } else { + final long delay = (Constants.RETRY_FIRST_DELAY * DateUtils.SECOND_IN_MILLIS + * (1 << (mNumFailed - 1))); + startAfter = mLastMod + fuzzDelay(delay); + } + return Math.max(0, startAfter - now); + } else { + return 0; + } + } + + /** + * Return the network type constraint required by this download. + * + * @see android.app.job.JobInfo.Builder#setRequiredNetworkType(int) + */ + public int getRequiredNetworkType(long totalBytes) { + if (!mAllowMetered) { + return JobInfo.NETWORK_TYPE_UNMETERED; + } + if (mAllowedNetworkTypes == DownloadManager.Request.NETWORK_WIFI) { + return JobInfo.NETWORK_TYPE_UNMETERED; + } + if (totalBytes > mSystemFacade.getMaxBytesOverMobile()) { + return JobInfo.NETWORK_TYPE_UNMETERED; } - if (mRetryAfter > 0) { - return mLastMod + mRetryAfter; + if (totalBytes > mSystemFacade.getRecommendedMaxBytesOverMobile() + && mBypassRecommendedSizeLimit == 0) { + return JobInfo.NETWORK_TYPE_UNMETERED; } - return mLastMod + - Constants.RETRY_FIRST_DELAY * - (1000 + mFuzz) * (1 << (mNumFailed - 1)); + if (!mAllowRoaming) { + return JobInfo.NETWORK_TYPE_NOT_ROAMING; + } + return JobInfo.NETWORK_TYPE_ANY; } /** - * Returns whether this download should be enqueued. + * Returns whether this download is ready to be scheduled. */ - private boolean isReadyToDownload() { + public boolean isReadyToSchedule() { if (mControl == Downloads.Impl.CONTROL_PAUSED) { // the download is paused, so it's not going to start return false; } switch (mStatus) { - case 0: // status hasn't been initialized yet, this is a new download - case Downloads.Impl.STATUS_PENDING: // download is explicit marked as ready to start - case Downloads.Impl.STATUS_RUNNING: // download interrupted (process killed etc) while - // running, without a chance to update the database - return true; - + case 0: + case Downloads.Impl.STATUS_PENDING: + case Downloads.Impl.STATUS_RUNNING: case Downloads.Impl.STATUS_WAITING_FOR_NETWORK: + case Downloads.Impl.STATUS_WAITING_TO_RETRY: case Downloads.Impl.STATUS_QUEUED_FOR_WIFI: - return checkCanUseNetwork(mTotalBytes) == NetworkState.OK; + return true; - 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? final Uri uri = Uri.parse(mUri); @@ -357,11 +348,10 @@ public class DownloadInfo { Log.w(TAG, "Expected file URI on external storage: " + mUri); return false; } - case Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR: - // avoids repetition of retrying download + + default: return false; } - return false; } /** @@ -378,27 +368,11 @@ public class DownloadInfo { return false; } - /** - * Returns whether this download is allowed to use the network. - */ - public NetworkState checkCanUseNetwork(long totalBytes) { - final NetworkInfo info = mSystemFacade.getActiveNetworkInfo(mUid); - if (info == null || !info.isConnected()) { - return NetworkState.NO_CONNECTION; - } - if (DetailedState.BLOCKED.equals(info.getDetailedState())) { - return NetworkState.BLOCKED; - } - if (mSystemFacade.isNetworkRoaming() && !isRoamingAllowed()) { - return NetworkState.CANNOT_USE_ROAMING; - } - if (mSystemFacade.isActiveNetworkMetered() && !mAllowMetered) { - return NetworkState.TYPE_DISALLOWED_BY_REQUESTOR; - } - return checkIsNetworkTypeAllowed(info.getType(), totalBytes); + public boolean isMeteredAllowed(long totalBytes) { + return getRequiredNetworkType(totalBytes) != JobInfo.NETWORK_TYPE_UNMETERED; } - private boolean isRoamingAllowed() { + public boolean isRoamingAllowed() { if (mIsPublicApi) { return mAllowRoaming; } else { // legacy behavior @@ -406,112 +380,6 @@ public class DownloadInfo { } } - /** - * Check if this download can proceed over the given network type. - * @param networkType a constant from ConnectivityManager.TYPE_*. - * @return one of the NETWORK_* constants - */ - private NetworkState checkIsNetworkTypeAllowed(int networkType, long totalBytes) { - if (mIsPublicApi) { - final int flag = translateNetworkTypeToApiFlag(networkType); - final boolean allowAllNetworkTypes = mAllowedNetworkTypes == ~0; - if (!allowAllNetworkTypes && (flag & mAllowedNetworkTypes) == 0) { - return NetworkState.TYPE_DISALLOWED_BY_REQUESTOR; - } - } - return checkSizeAllowedForNetwork(networkType, totalBytes); - } - - /** - * Translate a ConnectivityManager.TYPE_* constant to the corresponding - * DownloadManager.Request.NETWORK_* bit flag. - */ - private int translateNetworkTypeToApiFlag(int networkType) { - switch (networkType) { - case ConnectivityManager.TYPE_MOBILE: - return DownloadManager.Request.NETWORK_MOBILE; - - case ConnectivityManager.TYPE_WIFI: - return DownloadManager.Request.NETWORK_WIFI; - - case ConnectivityManager.TYPE_BLUETOOTH: - return DownloadManager.Request.NETWORK_BLUETOOTH; - - default: - return 0; - } - } - - /** - * Check if the download's size prohibits it from running over the current network. - * @return one of the NETWORK_* constants - */ - private NetworkState checkSizeAllowedForNetwork(int networkType, long totalBytes) { - if (totalBytes <= 0) { - // we don't know the size yet - return NetworkState.OK; - } - - if (ConnectivityManager.isNetworkTypeMobile(networkType)) { - Long maxBytesOverMobile = mSystemFacade.getMaxBytesOverMobile(); - if (maxBytesOverMobile != null && totalBytes > maxBytesOverMobile) { - return NetworkState.UNUSABLE_DUE_TO_SIZE; - } - if (mBypassRecommendedSizeLimit == 0) { - Long recommendedMaxBytesOverMobile = mSystemFacade - .getRecommendedMaxBytesOverMobile(); - if (recommendedMaxBytesOverMobile != null - && totalBytes > recommendedMaxBytesOverMobile) { - return NetworkState.RECOMMENDED_UNUSABLE_DUE_TO_SIZE; - } - } - } - - return NetworkState.OK; - } - - /** - * If download is ready to start, and isn't already pending or executing, - * create a {@link DownloadThread} and enqueue it into given - * {@link Executor}. - * - * @return If actively downloading. - */ - public boolean startDownloadIfReady(ExecutorService executor) { - synchronized (this) { - final boolean isReady = isReadyToDownload(); - final boolean isActive = mSubmittedTask != null && !mSubmittedTask.isDone(); - if (isReady && !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); - } - - mTask = new DownloadThread(mContext, mSystemFacade, mNotifier, this); - mSubmittedTask = executor.submit(mTask); - } - return isReady; - } - } - - /** - * If download is ready to be scanned, enqueue it into the given - * {@link DownloadScanner}. - * - * @return If actively scanning. - */ - public boolean startScanIfReady(DownloadScanner scanner) { - synchronized (this) { - final boolean isReady = shouldScanFile(); - if (isReady) { - scanner.requestScan(this); - } - return isReady; - } - } - public boolean isOnCache() { return (mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION || mDestination == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION @@ -571,70 +439,42 @@ public class DownloadInfo { pw.printPair("mAllowedNetworkTypes", mAllowedNetworkTypes); pw.printPair("mAllowRoaming", mAllowRoaming); pw.printPair("mAllowMetered", mAllowMetered); + pw.printPair("mFlags", mFlags); pw.println(); pw.decreaseIndent(); } /** - * Return time when this download will be ready for its next action, in - * milliseconds after given time. - * - * @return If {@code 0}, download is ready to proceed immediately. If - * {@link Long#MAX_VALUE}, then download has no future actions. - */ - public long nextActionMillis(long now) { - if (Downloads.Impl.isStatusCompleted(mStatus)) { - return Long.MAX_VALUE; - } - if (mStatus != Downloads.Impl.STATUS_WAITING_TO_RETRY) { - return 0; - } - long when = restartTime(now); - if (when <= now) { - return 0; - } - return when - now; - } - - /** * Returns whether a file should be scanned */ - public boolean shouldScanFile() { + public boolean shouldScanFile(int status) { return (mMediaScanned == 0) && (mDestination == Downloads.Impl.DESTINATION_EXTERNAL || mDestination == Downloads.Impl.DESTINATION_FILE_URI || mDestination == Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) - && Downloads.Impl.isStatusSuccess(mStatus); - } - - void notifyPauseDueToSize(boolean isWifiRequired) { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(getAllDownloadsUri()); - intent.setClassName(SizeLimitActivity.class.getPackage().getName(), - SizeLimitActivity.class.getName()); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(EXTRA_IS_WIFI_REQUIRED, isWifiRequired); - mContext.startActivity(intent); + && Downloads.Impl.isStatusSuccess(status); } /** * Query and return status of requested download. */ - public static int queryDownloadStatus(ContentResolver resolver, long id) { - final Cursor cursor = resolver.query( - ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id), - new String[] { Downloads.Impl.COLUMN_STATUS }, null, null, null); - try { + public int queryDownloadStatus() { + return queryDownloadInt(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING); + } + + public int queryDownloadControl() { + return queryDownloadInt(Downloads.Impl.COLUMN_CONTROL, Downloads.Impl.CONTROL_RUN); + } + + public int queryDownloadInt(String columnName, int defaultValue) { + try (Cursor cursor = mContext.getContentResolver().query(getAllDownloadsUri(), + new String[] { columnName }, null, null, null)) { if (cursor.moveToFirst()) { return cursor.getInt(0); } else { - // TODO: increase strictness of value returned for unknown - // downloads; this is safe default for now. - return Downloads.Impl.STATUS_PENDING; + return defaultValue; } - } finally { - cursor.close(); } } } |