From a04e9d0a00622f67bcc0d5a25229d0344259e0ba Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Thu, 16 Oct 2014 14:40:23 -0700 Subject: Trim stale downloads from third-party apps. Buggy third-party apps can enqueue lots of downloads and then forget to remove them, causing DownloadManager to stop functioning. This change removes any downloads that match _all_ of the following conditions: 1. Download status is in a terminal (non-pending) state, usually a concrete success or failure. 2. Download hasn't been touched in over a week. 3. Download is not visible in UI. Bug: 17785419 Change-Id: Id82752fd6935371c1af682205d35f7ba35169473 --- .../providers/downloads/DownloadIdleService.java | 52 +++++++++++++++++++--- 1 file changed, 47 insertions(+), 5 deletions(-) (limited to 'src/com') diff --git a/src/com/android/providers/downloads/DownloadIdleService.java b/src/com/android/providers/downloads/DownloadIdleService.java index ddfeba41..b5371552 100644 --- a/src/com/android/providers/downloads/DownloadIdleService.java +++ b/src/com/android/providers/downloads/DownloadIdleService.java @@ -29,6 +29,7 @@ import android.os.Environment; import android.provider.Downloads; import android.system.ErrnoException; import android.text.TextUtils; +import android.text.format.DateUtils; import android.util.Slog; import com.android.providers.downloads.StorageUtils.ConcreteFile; @@ -57,10 +58,11 @@ public class DownloadIdleService extends JobService { @Override public void run() { + cleanStale(); cleanOrphans(); jobFinished(mParams, false); } - }; + } @Override public boolean onStartJob(JobParameters params) { @@ -75,7 +77,47 @@ public class DownloadIdleService extends JobService { return false; } - private interface DownloadQuery { + private interface StaleQuery { + final String[] PROJECTION = new String[] { + Downloads.Impl._ID, + Downloads.Impl.COLUMN_STATUS, + Downloads.Impl.COLUMN_LAST_MODIFICATION, + Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI }; + + final int _ID = 0; + } + + /** + * Remove stale downloads that third-party apps probably forgot about. We + * only consider non-visible downloads that haven't been touched in over a + * week. + */ + public void cleanStale() { + final ContentResolver resolver = getContentResolver(); + + final long modifiedBefore = System.currentTimeMillis() - DateUtils.WEEK_IN_MILLIS; + final Cursor cursor = resolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, + StaleQuery.PROJECTION, Downloads.Impl.COLUMN_STATUS + " >= '200' AND " + + Downloads.Impl.COLUMN_LAST_MODIFICATION + " <= '" + modifiedBefore + + "' AND " + Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI + " == '0'", + null, null); + + int count = 0; + try { + while (cursor.moveToNext()) { + final long id = cursor.getLong(StaleQuery._ID); + resolver.delete(ContentUris.withAppendedId( + Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id), null, null); + count++; + } + } finally { + IoUtils.closeQuietly(cursor); + } + + Slog.d(TAG, "Removed " + count + " stale downloads"); + } + + private interface OrphanQuery { final String[] PROJECTION = new String[] { Downloads.Impl._ID, Downloads.Impl._DATA }; @@ -93,10 +135,10 @@ public class DownloadIdleService extends JobService { // Collect known files from database final HashSet fromDb = Sets.newHashSet(); final Cursor cursor = resolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, - DownloadQuery.PROJECTION, null, null, null); + OrphanQuery.PROJECTION, null, null, null); try { while (cursor.moveToNext()) { - final String path = cursor.getString(DownloadQuery._DATA); + final String path = cursor.getString(OrphanQuery._DATA); if (TextUtils.isEmpty(path)) continue; final File file = new File(path); @@ -111,7 +153,7 @@ public class DownloadIdleService extends JobService { // currently mounted device, so remove it from database. // This logic preserves files on external storage while // media is removed. - final long id = cursor.getLong(DownloadQuery._ID); + final long id = cursor.getLong(OrphanQuery._ID); Slog.d(TAG, "Missing " + file + ", deleting " + id); resolver.delete(ContentUris.withAppendedId( Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id), null, null); -- cgit v1.2.3 From 8026ba6242ac52fc05b4eea4891b7b18e41ca873 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Mon, 10 Nov 2014 10:06:14 -0800 Subject: Connection: close is enough to know length. Now that we're defeating connection reuse, we have one additional type of response where the length is known. Bug: 18306491 Change-Id: I19657c565238f07fd89a55a5dbf1e85748f6e7c3 --- src/com/android/providers/downloads/DownloadThread.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'src/com') diff --git a/src/com/android/providers/downloads/DownloadThread.java b/src/com/android/providers/downloads/DownloadThread.java index c0f5a5e9..2493adaf 100644 --- a/src/com/android/providers/downloads/DownloadThread.java +++ b/src/com/android/providers/downloads/DownloadThread.java @@ -439,12 +439,16 @@ public class DownloadThread implements Runnable { */ private void transferData(HttpURLConnection conn) throws StopRequestException { - // To detect when we're really finished, we either need a length or - // chunked encoding. + // To detect when we're really finished, we either need a length, closed + // connection, or chunked encoding. final boolean hasLength = mInfoDelta.mTotalBytes != -1; - final String transferEncoding = conn.getHeaderField("Transfer-Encoding"); - final boolean isChunked = "chunked".equalsIgnoreCase(transferEncoding); - if (!hasLength && !isChunked) { + final boolean isConnectionClose = "close".equalsIgnoreCase( + conn.getHeaderField("Connection")); + final boolean isEncodingChunked = "chunked".equalsIgnoreCase( + conn.getHeaderField("Transfer-Encoding")); + + final boolean finishKnown = hasLength || isConnectionClose || isEncodingChunked; + if (!finishKnown) { throw new StopRequestException( STATUS_CANNOT_RESUME, "can't know size of download, giving up"); } -- cgit v1.2.3 From 161e1c75acbadae42ab9d9f55a23f2e27fab8e83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lasse=20Brudeskar=20Vik=C3=A5s?= Date: Sun, 19 Jan 2014 22:01:57 +0100 Subject: Add Download Speeds in notification This commit adds the ability to show the current total download speed Screenshots: http://goo.gl/M3eRNR ps7 : remove download site, and make speed to bit/sec ps8 : make speed to byte/sec and cleanup ps9 : revert to B/sec ps10: fix some formatting issues ps13: rebased Conflicts: res/values/cm_strings.xml src/com/android/providers/downloads/DownloadNotifier.java Change-Id: I801dbe61c7ee59d0c1d14d5851ad6dc3a7678499 --- .../providers/downloads/DownloadNotifier.java | 32 ++++++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) (limited to 'src/com') diff --git a/src/com/android/providers/downloads/DownloadNotifier.java b/src/com/android/providers/downloads/DownloadNotifier.java index bfd5568d..0fb5a583 100644 --- a/src/com/android/providers/downloads/DownloadNotifier.java +++ b/src/com/android/providers/downloads/DownloadNotifier.java @@ -35,6 +35,7 @@ import android.os.SystemClock; import android.provider.Downloads; import android.text.TextUtils; import android.text.format.DateUtils; +import android.text.format.Formatter; import android.util.Log; import android.util.LongSparseLongArray; @@ -204,6 +205,7 @@ public class DownloadNotifier { // Calculate and show progress String remainingText = null; String percentText = null; + String speedAsSizeText = null; if (type == TYPE_ACTIVE) { long current = 0; long total = 0; @@ -224,8 +226,26 @@ public class DownloadNotifier { if (speed > 0) { final long remainingMillis = ((total - current) * 1000) / speed; + final int duration, durationResId; + + // This duplicates DateUtils.formatDuration(), but uses our + // abbreviated plurals. + if (remainingMillis >= DateUtils.HOUR_IN_MILLIS) { + duration = (int) ((remainingMillis + 1800000) + / DateUtils.HOUR_IN_MILLIS); + durationResId = R.plurals.duration_hours; + } else if (remainingMillis >= DateUtils.MINUTE_IN_MILLIS) { + duration = (int) ((remainingMillis + 30000) + / DateUtils.MINUTE_IN_MILLIS); + durationResId = R.plurals.duration_minutes; + } else { + duration = (int) ((remainingMillis + 500) + / DateUtils.SECOND_IN_MILLIS); + durationResId = R.plurals.duration_seconds; + } remainingText = res.getString(R.string.download_remaining, - DateUtils.formatDuration(remainingMillis)); + res.getQuantityString(durationResId, duration, duration)); + speedAsSizeText = Formatter.formatFileSize(mContext, speed); } builder.setProgress(100, percent, false); @@ -242,10 +262,9 @@ public class DownloadNotifier { builder.setContentTitle(getDownloadTitle(res, info)); if (type == TYPE_ACTIVE) { - if (!TextUtils.isEmpty(info.mDescription)) { - builder.setContentText(info.mDescription); - } else { - builder.setContentText(remainingText); + if (speedAsSizeText != null) { + builder.setContentText(res.getString(R.string.download_speed_text, + remainingText, speedAsSizeText)); } builder.setContentInfo(percentText); @@ -275,7 +294,8 @@ public class DownloadNotifier { builder.setContentTitle(res.getQuantityString( R.plurals.notif_summary_active, cluster.size(), cluster.size())); builder.setContentText(remainingText); - builder.setContentInfo(percentText); + builder.setContentInfo(res.getString(R.string.download_speed_text, + percentText, speedAsSizeText)); inboxStyle.setSummaryText(remainingText); } else if (type == TYPE_WAITING) { -- cgit v1.2.3 From 4359b2efa52ab524d70897a7dbe22d3dfb496de1 Mon Sep 17 00:00:00 2001 From: Danesh Mondegarian Date: Mon, 20 Apr 2015 23:28:14 -0700 Subject: DownloadManager : Only remove stale id if download is not deleted This addresses the issue where if a download is enqueued and right after deleted, the notification doesn't stick around. Change-Id: Ie1ec08fc94420b0f46e1543ed52afd8c22aee372 (cherry picked from commit 794ab54677c50bac5c0f32bc5d9ad768ff10c039) --- src/com/android/providers/downloads/DownloadService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/com') diff --git a/src/com/android/providers/downloads/DownloadService.java b/src/com/android/providers/downloads/DownloadService.java index 58cf380c..48aa2d84 100644 --- a/src/com/android/providers/downloads/DownloadService.java +++ b/src/com/android/providers/downloads/DownloadService.java @@ -364,7 +364,6 @@ public class DownloadService extends Service { final int idColumn = cursor.getColumnIndexOrThrow(Downloads.Impl._ID); while (cursor.moveToNext()) { final long id = cursor.getLong(idColumn); - staleIds.remove(id); DownloadInfo info = mDownloads.get(id); if (info != null) { @@ -396,6 +395,7 @@ public class DownloadService extends Service { isActive |= activeDownload; isActive |= activeScan; + staleIds.remove(id); } // Keep track of nearest next action -- cgit v1.2.3 From 04870c106b59eefdbcd93c3b16f37bf18e5fe4ad Mon Sep 17 00:00:00 2001 From: herriojr Date: Tue, 29 Sep 2015 15:13:38 -0700 Subject: Fixed application/octet-stream to guess mimetype When being downloaded, some files were being classified as application/octet-stream in the response even though they are a valid mimetype. This adds in guessing the mimetype before sending off the intent. It was chosen to put this here instead of when it is downloaded to make it backwards compatible with files that already exist in people's downloads. Change-Id: Id5fc9a3cca6851677b066854fcdccb5ae4fade7c Issue-Id: CYNGNOS-1153 --- src/com/android/providers/downloads/OpenHelper.java | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'src/com') diff --git a/src/com/android/providers/downloads/OpenHelper.java b/src/com/android/providers/downloads/OpenHelper.java index 4eb319c4..0a12daac 100644 --- a/src/com/android/providers/downloads/OpenHelper.java +++ b/src/com/android/providers/downloads/OpenHelper.java @@ -32,6 +32,7 @@ import android.database.Cursor; import android.net.Uri; import android.provider.Downloads.Impl.RequestHeaders; import android.util.Log; +import android.webkit.MimeTypeMap; import java.io.File; @@ -75,6 +76,14 @@ public class OpenHelper { final Uri localUri = getCursorUri(cursor, COLUMN_LOCAL_URI); final File file = getCursorFile(cursor, COLUMN_LOCAL_FILENAME); String mimeType = getCursorString(cursor, COLUMN_MEDIA_TYPE); + if ("application/octet-stream".equals(mimeType)) { + MimeTypeMap m = MimeTypeMap.getSingleton(); + String guess = m.getMimeTypeFromExtension( + m.getFileExtensionFromUrl(localUri.toString())); + if (guess != null) { + mimeType = guess; + } + } mimeType = DownloadDrmHelper.getOriginalMimeType(context, file, mimeType); final Intent intent = new Intent(Intent.ACTION_VIEW); -- cgit v1.2.3