diff options
author | Steve Howard <showard@google.com> | 2010-09-20 11:55:00 -0700 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2010-09-20 11:55:00 -0700 |
commit | 31eb4c1b778686307c3d217577dd304ea475064a (patch) | |
tree | c1afc28aaf00195a952dc50cbbe38c069c59f268 | |
parent | a5e23ea33b415bca6ca7572e3286d4ea04b62c8a (diff) | |
parent | b9115af1e7b39a25c00aed7c9d3bb413ecef9eda (diff) | |
download | android_packages_providers_DownloadProvider-31eb4c1b778686307c3d217577dd304ea475064a.tar.gz android_packages_providers_DownloadProvider-31eb4c1b778686307c3d217577dd304ea475064a.tar.bz2 android_packages_providers_DownloadProvider-31eb4c1b778686307c3d217577dd304ea475064a.zip |
am b9115af1: am 3398db8f: Fix notification bugs, cleanup DownloadService + DownloadReceiver
Merge commit 'b9115af1e7b39a25c00aed7c9d3bb413ecef9eda'
* commit 'b9115af1e7b39a25c00aed7c9d3bb413ecef9eda':
Fix notification bugs, cleanup DownloadService + DownloadReceiver
9 files changed, 476 insertions, 646 deletions
diff --git a/src/com/android/providers/downloads/DownloadInfo.java b/src/com/android/providers/downloads/DownloadInfo.java index 0cf025b8..eb9ac4bd 100644 --- a/src/com/android/providers/downloads/DownloadInfo.java +++ b/src/com/android/providers/downloads/DownloadInfo.java @@ -16,11 +16,14 @@ package com.android.providers.downloads; +import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.Intent; +import android.database.CharArrayBuffer; import android.database.Cursor; +import android.drm.mobile1.DrmRawContent; import android.net.ConnectivityManager; import android.net.DownloadManager; import android.net.Uri; @@ -36,7 +39,127 @@ import java.util.Map; * Stores information about an individual download. */ public class DownloadInfo { - public int mId; + public static class Reader { + private ContentResolver mResolver; + private Cursor mCursor; + private CharArrayBuffer mOldChars; + private CharArrayBuffer mNewChars; + + public Reader(ContentResolver resolver, Cursor cursor) { + mResolver = resolver; + mCursor = cursor; + } + + public DownloadInfo newDownloadInfo(Context context, SystemFacade systemFacade) { + DownloadInfo info = new DownloadInfo(context, systemFacade); + updateFromDatabase(info); + readRequestHeaders(info); + return info; + } + + public void updateFromDatabase(DownloadInfo info) { + info.mId = getLong(Downloads.Impl._ID); + info.mUri = getString(info.mUri, Downloads.Impl.COLUMN_URI); + info.mNoIntegrity = getInt(Downloads.Impl.COLUMN_NO_INTEGRITY) == 1; + info.mHint = getString(info.mHint, Downloads.Impl.COLUMN_FILE_NAME_HINT); + info.mFileName = getString(info.mFileName, Downloads.Impl._DATA); + info.mMimeType = getString(info.mMimeType, Downloads.Impl.COLUMN_MIME_TYPE); + info.mDestination = getInt(Downloads.Impl.COLUMN_DESTINATION); + info.mVisibility = getInt(Downloads.Impl.COLUMN_VISIBILITY); + info.mStatus = getInt(Downloads.Impl.COLUMN_STATUS); + info.mNumFailed = getInt(Constants.FAILED_CONNECTIONS); + int retryRedirect = getInt(Constants.RETRY_AFTER_X_REDIRECT_COUNT); + info.mRetryAfter = retryRedirect & 0xfffffff; + info.mRedirectCount = retryRedirect >> 28; + info.mLastMod = getLong(Downloads.Impl.COLUMN_LAST_MODIFICATION); + info.mPackage = getString(info.mPackage, Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE); + info.mClass = getString(info.mClass, Downloads.Impl.COLUMN_NOTIFICATION_CLASS); + info.mExtras = getString(info.mExtras, Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS); + info.mCookies = getString(info.mCookies, Downloads.Impl.COLUMN_COOKIE_DATA); + info.mUserAgent = getString(info.mUserAgent, Downloads.Impl.COLUMN_USER_AGENT); + info.mReferer = getString(info.mReferer, Downloads.Impl.COLUMN_REFERER); + info.mTotalBytes = getLong(Downloads.Impl.COLUMN_TOTAL_BYTES); + info.mCurrentBytes = getLong(Downloads.Impl.COLUMN_CURRENT_BYTES); + info.mETag = getString(info.mETag, Constants.ETAG); + info.mMediaScanned = getInt(Constants.MEDIA_SCANNED) == 1; + info.mIsPublicApi = getInt(Downloads.Impl.COLUMN_IS_PUBLIC_API) != 0; + info.mAllowedNetworkTypes = getInt(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES); + info.mAllowRoaming = getInt(Downloads.Impl.COLUMN_ALLOW_ROAMING) != 0; + info.mTitle = getString(info.mTitle, Downloads.Impl.COLUMN_TITLE); + info.mDescription = getString(info.mDescription, Downloads.Impl.COLUMN_DESCRIPTION); + + synchronized (this) { + info.mControl = getInt(Downloads.Impl.COLUMN_CONTROL); + } + } + + private void readRequestHeaders(DownloadInfo info) { + info.mRequestHeaders.clear(); + Uri headerUri = Uri.withAppendedPath( + info.getAllDownloadsUri(), Downloads.Impl.RequestHeaders.URI_SEGMENT); + Cursor cursor = mResolver.query(headerUri, null, null, null, null); + try { + int headerIndex = + cursor.getColumnIndexOrThrow(Downloads.Impl.RequestHeaders.COLUMN_HEADER); + int valueIndex = + cursor.getColumnIndexOrThrow(Downloads.Impl.RequestHeaders.COLUMN_VALUE); + for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { + info.mRequestHeaders.put( + cursor.getString(headerIndex), cursor.getString(valueIndex)); + } + } finally { + cursor.close(); + } + + if (info.mCookies != null) { + info.mRequestHeaders.put("Cookie", info.mCookies); + } + if (info.mReferer != null) { + info.mRequestHeaders.put("Referer", info.mReferer); + } + } + + /** + * Returns a String that holds the current value of the column, optimizing for the case + * where the value hasn't changed. + */ + private String getString(String old, String column) { + int index = mCursor.getColumnIndexOrThrow(column); + if (old == null) { + return mCursor.getString(index); + } + if (mNewChars == null) { + mNewChars = new CharArrayBuffer(128); + } + mCursor.copyStringToBuffer(index, mNewChars); + int length = mNewChars.sizeCopied; + if (length != old.length()) { + return new String(mNewChars.data, 0, length); + } + if (mOldChars == null || mOldChars.sizeCopied < length) { + mOldChars = new CharArrayBuffer(length); + } + char[] oldArray = mOldChars.data; + char[] newArray = mNewChars.data; + old.getChars(0, length, oldArray, 0); + for (int i = length - 1; i >= 0; --i) { + if (oldArray[i] != newArray[i]) { + return new String(newArray, 0, length); + } + } + return old; + } + + private Integer getInt(String column) { + return mCursor.getInt(mCursor.getColumnIndexOrThrow(column)); + } + + private Long getLong(String column) { + return mCursor.getLong(mCursor.getColumnIndexOrThrow(column)); + } + } + + public long mId; public String mUri; public boolean mNoIntegrity; public String mHint; @@ -75,83 +198,10 @@ public class DownloadInfo { private SystemFacade mSystemFacade; private Context mContext; - public DownloadInfo(Context context, SystemFacade systemFacade, Cursor cursor) { + private DownloadInfo(Context context, SystemFacade systemFacade) { mContext = context; mSystemFacade = systemFacade; - - int retryRedirect = - cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER_X_REDIRECT_COUNT)); - mId = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl._ID)); - mUri = cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_URI)); - mNoIntegrity = cursor.getInt(cursor.getColumnIndexOrThrow( - Downloads.Impl.COLUMN_NO_INTEGRITY)) == 1; - mHint = cursor.getString(cursor.getColumnIndexOrThrow( - Downloads.Impl.COLUMN_FILE_NAME_HINT)); - mFileName = cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl._DATA)); - mMimeType = cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_MIME_TYPE)); - mDestination = - cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_DESTINATION)); - mVisibility = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_VISIBILITY)); - mControl = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_CONTROL)); - mStatus = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_STATUS)); - mNumFailed = cursor.getInt(cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS)); - mRetryAfter = retryRedirect & 0xfffffff; - mRedirectCount = retryRedirect >> 28; - mLastMod = cursor.getLong(cursor.getColumnIndexOrThrow( - Downloads.Impl.COLUMN_LAST_MODIFICATION)); - mPackage = cursor.getString(cursor.getColumnIndexOrThrow( - Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE)); - mClass = cursor.getString(cursor.getColumnIndexOrThrow( - Downloads.Impl.COLUMN_NOTIFICATION_CLASS)); - mExtras = cursor.getString(cursor.getColumnIndexOrThrow( - Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS)); - mCookies = - cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_COOKIE_DATA)); - mUserAgent = - cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_USER_AGENT)); - mReferer = cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_REFERER)); - mTotalBytes = - cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_TOTAL_BYTES)); - mCurrentBytes = - cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_CURRENT_BYTES)); - mETag = cursor.getString(cursor.getColumnIndexOrThrow(Constants.ETAG)); - mMediaScanned = cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1; - mIsPublicApi = cursor.getInt( - cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_IS_PUBLIC_API)) != 0; - mAllowedNetworkTypes = cursor.getInt( - cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES)); - mAllowRoaming = cursor.getInt( - cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_ALLOW_ROAMING)) != 0; - mTitle = cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_TITLE)); - mDescription = - cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_DESCRIPTION)); mFuzz = Helpers.sRandom.nextInt(1001); - - readRequestHeaders(mId); - } - - private void readRequestHeaders(long downloadId) { - Uri headerUri = Uri.withAppendedPath( - getAllDownloadsUri(), Downloads.Impl.RequestHeaders.URI_SEGMENT); - Cursor cursor = mContext.getContentResolver().query(headerUri, null, null, null, null); - try { - int headerIndex = - cursor.getColumnIndexOrThrow(Downloads.Impl.RequestHeaders.COLUMN_HEADER); - int valueIndex = - cursor.getColumnIndexOrThrow(Downloads.Impl.RequestHeaders.COLUMN_VALUE); - for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { - mRequestHeaders.put(cursor.getString(headerIndex), cursor.getString(valueIndex)); - } - } finally { - cursor.close(); - } - - if (mCookies != null) { - mRequestHeaders.put("Cookie", mCookies); - } - if (mReferer != null) { - mRequestHeaders.put("Referer", mReferer); - } } public Map<String, String> getHeaders() { @@ -167,7 +217,7 @@ public class DownloadInfo { if (mIsPublicApi) { intent = new Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETE); intent.setPackage(mPackage); - intent.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, (long) mId); + intent.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, mId); } else { // legacy behavior if (mClass == null) { return; @@ -393,4 +443,66 @@ public class DownloadInfo { public Uri getAllDownloadsUri() { return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, mId); } + + + public void logVerboseInfo() { + Log.v(Constants.TAG, "Service adding new entry"); + Log.v(Constants.TAG, "ID : " + mId); + Log.v(Constants.TAG, "URI : " + ((mUri != null) ? "yes" : "no")); + Log.v(Constants.TAG, "NO_INTEG: " + mNoIntegrity); + Log.v(Constants.TAG, "HINT : " + mHint); + Log.v(Constants.TAG, "FILENAME: " + mFileName); + Log.v(Constants.TAG, "MIMETYPE: " + mMimeType); + Log.v(Constants.TAG, "DESTINAT: " + mDestination); + Log.v(Constants.TAG, "VISIBILI: " + mVisibility); + Log.v(Constants.TAG, "CONTROL : " + mControl); + Log.v(Constants.TAG, "STATUS : " + mStatus); + Log.v(Constants.TAG, "FAILED_C: " + mNumFailed); + Log.v(Constants.TAG, "RETRY_AF: " + mRetryAfter); + Log.v(Constants.TAG, "REDIRECT: " + mRedirectCount); + Log.v(Constants.TAG, "LAST_MOD: " + mLastMod); + Log.v(Constants.TAG, "PACKAGE : " + mPackage); + Log.v(Constants.TAG, "CLASS : " + mClass); + Log.v(Constants.TAG, "COOKIES : " + ((mCookies != null) ? "yes" : "no")); + Log.v(Constants.TAG, "AGENT : " + mUserAgent); + Log.v(Constants.TAG, "REFERER : " + ((mReferer != null) ? "yes" : "no")); + Log.v(Constants.TAG, "TOTAL : " + mTotalBytes); + Log.v(Constants.TAG, "CURRENT : " + mCurrentBytes); + Log.v(Constants.TAG, "ETAG : " + mETag); + Log.v(Constants.TAG, "SCANNED : " + mMediaScanned); + } + + /** + * Returns the amount of time (as measured from the "now" parameter) + * at which a download will be active. + * 0 = immediately - service should stick around to handle this download. + * -1 = never - service can go away without ever waking up. + * positive value - service must wake up in the future, as specified in ms from "now" + */ + long nextAction(long now) { + if (Downloads.Impl.isStatusCompleted(mStatus)) { + return -1; + } + if (mStatus != Downloads.Impl.STATUS_RUNNING_PAUSED) { + return 0; + } + if (mNumFailed == 0) { + return 0; + } + long when = restartTime(); + if (when <= now) { + return 0; + } + return when - now; + } + + /** + * Returns whether a file should be scanned + */ + boolean shouldScanFile() { + return !mMediaScanned + && mDestination == Downloads.Impl.DESTINATION_EXTERNAL + && Downloads.Impl.isStatusSuccess(mStatus) + && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(mMimeType); + } } diff --git a/src/com/android/providers/downloads/DownloadNotification.java b/src/com/android/providers/downloads/DownloadNotification.java index 38def594..90c8693b 100644 --- a/src/com/android/providers/downloads/DownloadNotification.java +++ b/src/com/android/providers/downloads/DownloadNotification.java @@ -26,8 +26,8 @@ import android.provider.Downloads; import android.view.View; import android.widget.RemoteViews; +import java.util.Collection; import java.util.HashMap; -import java.util.List; /** * This class handles the updating of the Notification Manager for the @@ -104,12 +104,12 @@ class DownloadNotification { /* * Update the notification ui. */ - public void updateNotification(List<DownloadInfo> downloads) { + public void updateNotification(Collection<DownloadInfo> downloads) { updateActiveNotification(downloads); updateCompletedNotification(downloads); } - private void updateActiveNotification(List<DownloadInfo> downloads) { + private void updateActiveNotification(Collection<DownloadInfo> downloads) { // Collate the notifications mNotifications.clear(); for (DownloadInfo download : downloads) { @@ -135,7 +135,6 @@ class DownloadNotification { item.mId = (int) id; item.mPackageName = packageName; item.mDescription = download.mDescription; - String className = download.mClass; item.addItem(title, progress, max); mNotifications.put(packageName, item); } @@ -195,7 +194,8 @@ class DownloadNotification { Intent intent = new Intent(Constants.ACTION_LIST); intent.setClassName("com.android.providers.downloads", DownloadReceiver.class.getName()); - intent.setData(ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, item.mId)); + intent.setData( + ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, item.mId)); intent.putExtra("multiple", item.mTitleCount > 1); n.contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); @@ -209,7 +209,7 @@ class DownloadNotification { return download.mStatus == Downloads.STATUS_RUNNING_PAUSED && download.mPausedReason != null; } - private void updateCompletedNotification(List<DownloadInfo> downloads) { + private void updateCompletedNotification(Collection<DownloadInfo> downloads) { for (DownloadInfo download : downloads) { if (!isCompleteAndVisible(download)) { continue; @@ -224,7 +224,8 @@ class DownloadNotification { title = mContext.getResources().getString( R.string.download_unknown_title); } - Uri contentUri = ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, id); + Uri contentUri = + ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id); String caption; Intent intent; if (Downloads.Impl.isStatusError(download.mStatus)) { diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java index 02b1fe12..23584f85 100644 --- a/src/com/android/providers/downloads/DownloadProvider.java +++ b/src/com/android/providers/downloads/DownloadProvider.java @@ -822,9 +822,8 @@ public final class DownloadProvider extends ContentProvider { if (filename != null) { Cursor c = query(uri, new String[] { Downloads.Impl.COLUMN_TITLE }, null, null, null); - if (!c.moveToFirst() || c.getString(0) == null) { - values.put(Downloads.Impl.COLUMN_TITLE, - new File(filename).getName()); + if (!c.moveToFirst() || c.getString(0).isEmpty()) { + values.put(Downloads.Impl.COLUMN_TITLE, new File(filename).getName()); } c.close(); } diff --git a/src/com/android/providers/downloads/DownloadReceiver.java b/src/com/android/providers/downloads/DownloadReceiver.java index 852c3712..d81e0266 100644 --- a/src/com/android/providers/downloads/DownloadReceiver.java +++ b/src/com/android/providers/downloads/DownloadReceiver.java @@ -28,7 +28,6 @@ import android.net.DownloadManager; import android.net.NetworkInfo; import android.net.Uri; import android.provider.Downloads; -import android.util.Config; import android.util.Log; import com.google.common.annotations.VisibleForTesting; @@ -47,146 +46,148 @@ public class DownloadReceiver extends BroadcastReceiver { mSystemFacade = new RealSystemFacade(context); } - if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Receiver onBoot"); - } - context.startService(new Intent(context, DownloadService.class)); - } else if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Receiver onConnectivity"); - } + String action = intent.getAction(); + if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { + startService(context); + } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); if (info != null && info.isConnected()) { - if (Constants.LOGX) { - if (Helpers.isNetworkAvailable(mSystemFacade)) { - Log.i(Constants.TAG, "Broadcast: Network Up"); - } else { - Log.i(Constants.TAG, "Broadcast: Network Up, Actually Down"); - } - } - context.startService(new Intent(context, DownloadService.class)); - } else { - if (Constants.LOGX) { - if (Helpers.isNetworkAvailable(mSystemFacade)) { - Log.i(Constants.TAG, "Broadcast: Network Down, Actually Up"); - } else { - Log.i(Constants.TAG, "Broadcast: Network Down"); - } - } + startService(context); } - } else if (intent.getAction().equals(Constants.ACTION_RETRY)) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Receiver retry"); + } else if (action.equals(Constants.ACTION_RETRY)) { + startService(context); + } else if (action.equals(Constants.ACTION_OPEN) + || action.equals(Constants.ACTION_LIST) + || action.equals(Constants.ACTION_HIDE)) { + handleNotificationBroadcast(context, intent); + } + } + + /** + * Handle any broadcast related to a system notification. + */ + private void handleNotificationBroadcast(Context context, Intent intent) { + Uri uri = intent.getData(); + String action = intent.getAction(); + if (Constants.LOGVV) { + if (action.equals(Constants.ACTION_OPEN)) { + Log.v(Constants.TAG, "Receiver open for " + uri); + } else if (action.equals(Constants.ACTION_LIST)) { + Log.v(Constants.TAG, "Receiver list for " + uri); + } else { // ACTION_HIDE + Log.v(Constants.TAG, "Receiver hide for " + uri); } - context.startService(new Intent(context, DownloadService.class)); - } else if (intent.getAction().equals(Constants.ACTION_OPEN) - || intent.getAction().equals(Constants.ACTION_LIST)) { - if (Constants.LOGVV) { - if (intent.getAction().equals(Constants.ACTION_OPEN)) { - Log.v(Constants.TAG, "Receiver open for " + intent.getData()); - } else { - Log.v(Constants.TAG, "Receiver list for " + intent.getData()); - } + } + + Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); + if (cursor == null) { + return; + } + try { + if (!cursor.moveToFirst()) { + return; } - Cursor cursor = context.getContentResolver().query( - intent.getData(), null, null, null, null); - if (cursor != null) { - if (cursor.moveToFirst()) { - int statusColumn = cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_STATUS); - int status = cursor.getInt(statusColumn); - int visibilityColumn = - cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_VISIBILITY); - int visibility = cursor.getInt(visibilityColumn); - if (Downloads.Impl.isStatusCompleted(status) - && visibility == Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) { - ContentValues values = new ContentValues(); - values.put(Downloads.Impl.COLUMN_VISIBILITY, - Downloads.Impl.VISIBILITY_VISIBLE); - context.getContentResolver().update(intent.getData(), values, null, null); - } - - if (intent.getAction().equals(Constants.ACTION_OPEN)) { - int filenameColumn = cursor.getColumnIndexOrThrow(Downloads.Impl._DATA); - int mimetypeColumn = - cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_MIME_TYPE); - String filename = cursor.getString(filenameColumn); - String mimetype = cursor.getString(mimetypeColumn); - Uri path = Uri.parse(filename); - // If there is no scheme, then it must be a file - if (path.getScheme() == null) { - path = Uri.fromFile(new File(filename)); - } - Intent activityIntent = new Intent(Intent.ACTION_VIEW); - activityIntent.setDataAndType(path, mimetype); - activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - try { - context.startActivity(activityIntent); - } catch (ActivityNotFoundException ex) { - if (Config.LOGD) { - Log.d(Constants.TAG, "no activity for " + mimetype, ex); - } - // nothing anyone can do about this, but we're in a clean state, - // swallow the exception entirely - } - } else { - int packageColumn = cursor.getColumnIndexOrThrow( - Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE); - int classColumn = cursor.getColumnIndexOrThrow( - Downloads.Impl.COLUMN_NOTIFICATION_CLASS); - int isPublicApiColumn = cursor.getColumnIndex( - Downloads.Impl.COLUMN_IS_PUBLIC_API); - String pckg = cursor.getString(packageColumn); - String clazz = cursor.getString(classColumn); - boolean isPublicApi = cursor.getInt(isPublicApiColumn) != 0; - - if (pckg != null) { - Intent appIntent = null; - if (isPublicApi) { - appIntent = new Intent(DownloadManager.ACTION_NOTIFICATION_CLICKED); - appIntent.setPackage(pckg); - } else if (clazz != null) { // legacy behavior - appIntent = new Intent(Downloads.Impl.ACTION_NOTIFICATION_CLICKED); - appIntent.setClassName(pckg, clazz); - if (intent.getBooleanExtra("multiple", true)) { - appIntent.setData(Downloads.Impl.CONTENT_URI); - } else { - appIntent.setData(intent.getData()); - } - } - if (appIntent != null) { - mSystemFacade.sendBroadcast(appIntent); - } - } - } - } - cursor.close(); + + if (action.equals(Constants.ACTION_OPEN)) { + openDownload(context, cursor); + hideNotification(context, uri, cursor); + } else if (action.equals(Constants.ACTION_LIST)) { + sendNotificationClickedIntent(intent, cursor); + } else { // ACTION_HIDE + hideNotification(context, uri, cursor); } - mSystemFacade.cancelNotification((int) ContentUris.parseId(intent.getData())); - } else if (intent.getAction().equals(Constants.ACTION_HIDE)) { - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Receiver hide for " + intent.getData()); + } finally { + cursor.close(); + } + } + + /** + * Hide a system notification for a download. + * @param uri URI to update the download + * @param cursor Cursor for reading the download's fields + */ + private void hideNotification(Context context, Uri uri, Cursor cursor) { + mSystemFacade.cancelNotification(ContentUris.parseId(uri)); + + int statusColumn = cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_STATUS); + int status = cursor.getInt(statusColumn); + int visibilityColumn = + cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_VISIBILITY); + int visibility = cursor.getInt(visibilityColumn); + if (Downloads.Impl.isStatusCompleted(status) + && visibility == Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) { + ContentValues values = new ContentValues(); + values.put(Downloads.Impl.COLUMN_VISIBILITY, + Downloads.Impl.VISIBILITY_VISIBLE); + context.getContentResolver().update(uri, values, null, null); + } + } + + /** + * Open the download that cursor is currently pointing to, since it's completed notification + * has been clicked. + */ + private void openDownload(Context context, Cursor cursor) { + String filename = cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl._DATA)); + String mimetype = + cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_MIME_TYPE)); + Uri path = Uri.parse(filename); + // If there is no scheme, then it must be a file + if (path.getScheme() == null) { + path = Uri.fromFile(new File(filename)); + } + + Intent activityIntent = new Intent(Intent.ACTION_VIEW); + activityIntent.setDataAndType(path, mimetype); + activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + context.startActivity(activityIntent); + } catch (ActivityNotFoundException ex) { + Log.d(Constants.TAG, "no activity for " + mimetype, ex); + } + } + + /** + * Notify the owner of a running download that its notification was clicked. + * @param intent the broadcast intent sent by the notification manager + * @param cursor Cursor for reading the download's fields + */ + private void sendNotificationClickedIntent(Intent intent, Cursor cursor) { + String pckg = cursor.getString( + cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE)); + if (pckg == null) { + return; + } + + String clazz = cursor.getString( + cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_NOTIFICATION_CLASS)); + boolean isPublicApi = + cursor.getInt(cursor.getColumnIndex(Downloads.Impl.COLUMN_IS_PUBLIC_API)) != 0; + + Intent appIntent = null; + if (isPublicApi) { + appIntent = new Intent(DownloadManager.ACTION_NOTIFICATION_CLICKED); + appIntent.setPackage(pckg); + } else { // legacy behavior + if (clazz == null) { + return; } - Cursor cursor = context.getContentResolver().query( - intent.getData(), null, null, null, null); - if (cursor != null) { - if (cursor.moveToFirst()) { - int statusColumn = cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_STATUS); - int status = cursor.getInt(statusColumn); - int visibilityColumn = - cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_VISIBILITY); - int visibility = cursor.getInt(visibilityColumn); - if (Downloads.Impl.isStatusCompleted(status) - && visibility == Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) { - ContentValues values = new ContentValues(); - values.put(Downloads.Impl.COLUMN_VISIBILITY, - Downloads.Impl.VISIBILITY_VISIBLE); - context.getContentResolver().update(intent.getData(), values, null, null); - } - } - cursor.close(); + appIntent = new Intent(Downloads.Impl.ACTION_NOTIFICATION_CLICKED); + appIntent.setClassName(pckg, clazz); + if (intent.getBooleanExtra("multiple", true)) { + appIntent.setData(Downloads.Impl.CONTENT_URI); + } else { + long downloadId = cursor.getLong(cursor.getColumnIndexOrThrow(Downloads.Impl._ID)); + appIntent.setData( + ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, downloadId)); } } + + mSystemFacade.sendBroadcast(appIntent); + } + + private void startService(Context context) { + context.startService(new Intent(context, DownloadService.class)); } } diff --git a/src/com/android/providers/downloads/DownloadService.java b/src/com/android/providers/downloads/DownloadService.java index b85fb902..62598b7c 100644 --- a/src/com/android/providers/downloads/DownloadService.java +++ b/src/com/android/providers/downloads/DownloadService.java @@ -25,12 +25,8 @@ import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.database.CharArrayBuffer; import android.database.ContentObserver; import android.database.Cursor; -import android.drm.mobile1.DrmRawContent; import android.media.IMediaScannerService; import android.net.Uri; import android.os.Environment; @@ -39,27 +35,22 @@ import android.os.IBinder; import android.os.Process; import android.os.RemoteException; import android.provider.Downloads; -import android.util.Config; import android.util.Log; -import com.google.android.collect.Lists; +import com.google.android.collect.Maps; import com.google.common.annotations.VisibleForTesting; import java.io.File; -import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; +import java.util.Map; +import java.util.Set; /** * Performs the background downloads requested by applications that use the Downloads provider. */ public class DownloadService extends Service { - - /* ------------ Constants ------------ */ - - /* ------------ Members ------------ */ - /** Observer to get notified when the content observer's data changes */ private DownloadManagerContentObserver mObserver; @@ -67,12 +58,12 @@ public class DownloadService extends Service { private DownloadNotification mNotifier; /** - * The Service's view of the list of downloads. This is kept independently - * from the content provider, and the Service only initiates downloads - * based on this data, so that it can deal with situation where the data - * in the content provider changes or disappears. + * The Service's view of the list of downloads, mapping download IDs to the corresponding info + * object. This is kept independently from the content provider, and the Service only initiates + * downloads based on this data, so that it can deal with situation where the data in the + * content provider changes or disappears. */ - private ArrayList<DownloadInfo> mDownloads; + private Map<Long, DownloadInfo> mDownloads = Maps.newHashMap(); /** * The thread that updates the internal download list from the content @@ -100,21 +91,9 @@ public class DownloadService extends Service { */ private IMediaScannerService mMediaScannerService; - /** - * Array used when extracting strings from content provider - */ - private CharArrayBuffer oldChars; - - /** - * Array used when extracting strings from content provider - */ - private CharArrayBuffer mNewChars; - @VisibleForTesting SystemFacade mSystemFacade; - /* ------------ Inner Classes ------------ */ - /** * Receives notifications when the data in the content provider changes */ @@ -183,8 +162,6 @@ public class DownloadService extends Service { } } - /* ------------ Methods ------------ */ - /** * Returns an IBinder instance when someone wants to connect to this * service. Binding to this service is not allowed. @@ -208,8 +185,6 @@ public class DownloadService extends Service { mSystemFacade = new RealSystemFacade(this); } - mDownloads = Lists.newArrayList(); - mObserver = new DownloadManagerContentObserver(); getContentResolver().registerContentObserver(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, true, mObserver); @@ -220,23 +195,20 @@ public class DownloadService extends Service { mNotifier = new DownloadNotification(this, mSystemFacade); mSystemFacade.cancelAllNotifications(); - mNotifier.updateNotification(mDownloads); trimDatabase(); removeSpuriousFiles(); updateFromProvider(); } - /** - * Responds to a call to startService - */ - public void onStart(Intent intent, int startId) { - super.onStart(intent, startId); + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + int returnValue = super.onStartCommand(intent, flags, startId); if (Constants.LOGVV) { Log.v(Constants.TAG, "Service onStart"); } - updateFromProvider(); + return returnValue; } /** @@ -287,189 +259,101 @@ public class DownloadService extends Service { stopSelf(); } if (wakeUp != Long.MAX_VALUE) { - AlarmManager alarms = - (AlarmManager) getSystemService(Context.ALARM_SERVICE); - if (alarms == null) { - Log.e(Constants.TAG, "couldn't get alarm manager"); - } else { - if (Constants.LOGV) { - Log.v(Constants.TAG, "scheduling retry in " + wakeUp + "ms"); - } - Intent intent = new Intent(Constants.ACTION_RETRY); - intent.setClassName("com.android.providers.downloads", - DownloadReceiver.class.getName()); - alarms.set( - AlarmManager.RTC_WAKEUP, - mSystemFacade.currentTimeMillis() + wakeUp, - PendingIntent.getBroadcast(DownloadService.this, 0, intent, - PendingIntent.FLAG_ONE_SHOT)); - } + scheduleAlarm(wakeUp); } - oldChars = null; - mNewChars = null; return; } mPendingUpdate = false; } + long now = mSystemFacade.currentTimeMillis(); + boolean mustScan = false; + keepService = false; + wakeUp = Long.MAX_VALUE; + Set<Long> idsNoLongerInDatabase = new HashSet<Long>(mDownloads.keySet()); Cursor cursor = getContentResolver().query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, - null, null, null, Downloads.Impl._ID); - + null, null, null, null); if (cursor == null) { - // TODO: this doesn't look right, it'd leave the loop in an inconsistent state - return; + continue; } + try { + DownloadInfo.Reader reader = + new DownloadInfo.Reader(getContentResolver(), cursor); + int idColumn = cursor.getColumnIndexOrThrow(Downloads.Impl._ID); + + for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { + long id = cursor.getLong(idColumn); + idsNoLongerInDatabase.remove(id); + DownloadInfo info = mDownloads.get(id); + if (info != null) { + updateDownload(reader, info, now); + } else { + info = insertDownload(reader, now); + } - cursor.moveToFirst(); - - int arrayPos = 0; - - boolean mustScan = false; - keepService = false; - wakeUp = Long.MAX_VALUE; - - boolean isAfterLast = cursor.isAfterLast(); - - int idColumn = cursor.getColumnIndexOrThrow(Downloads.Impl._ID); - - /* - * Walk the cursor and the local array to keep them in sync. The key - * to the algorithm is that the ids are unique and sorted both in - * the cursor and in the array, so that they can be processed in - * order in both sources at the same time: at each step, both - * sources point to the lowest id that hasn't been processed from - * that source, and the algorithm processes the lowest id from - * those two possibilities. - * At each step: - * -If the array contains an entry that's not in the cursor, remove the - * entry, move to next entry in the array. - * -If the array contains an entry that's in the cursor, nothing to do, - * move to next cursor row and next array entry. - * -If the cursor contains an entry that's not in the array, insert - * a new entry in the array, move to next cursor row and next - * array entry. - */ - while (!isAfterLast || arrayPos < mDownloads.size()) { - if (isAfterLast) { - // We're beyond the end of the cursor but there's still some - // stuff in the local array, which can only be junk - if (Constants.LOGVV) { - int arrayId = ((DownloadInfo) mDownloads.get(arrayPos)).mId; - Log.v(Constants.TAG, "Array update: trimming " + - arrayId + " @ " + arrayPos); + if (info.shouldScanFile() && !scanFile(info, true)) { + mustScan = true; + keepService = true; } - if (shouldScanFile(arrayPos) && mediaScannerConnected()) { - scanFile(null, arrayPos); + if (info.hasCompletionNotification()) { + keepService = true; } - deleteDownload(arrayPos); // this advances in the array - } else { - int id = cursor.getInt(idColumn); - - if (arrayPos == mDownloads.size()) { - insertDownload(cursor, arrayPos, now); - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Array update: appending " + - id + " @ " + arrayPos); - } - if (shouldScanFile(arrayPos) - && (!mediaScannerConnected() || !scanFile(cursor, arrayPos))) { - mustScan = true; - keepService = true; - } - if (visibleNotification(arrayPos)) { - keepService = true; - } - long next = nextAction(arrayPos, now); - if (next == 0) { - keepService = true; - } else if (next > 0 && next < wakeUp) { - wakeUp = next; - } - ++arrayPos; - cursor.moveToNext(); - isAfterLast = cursor.isAfterLast(); - } else { - int arrayId = mDownloads.get(arrayPos).mId; - - if (arrayId < id) { - // The array entry isn't in the cursor - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Array update: removing " + arrayId - + " @ " + arrayPos); - } - if (shouldScanFile(arrayPos) && mediaScannerConnected()) { - scanFile(null, arrayPos); - } - deleteDownload(arrayPos); // this advances in the array - } else if (arrayId == id) { - // This cursor row already exists in the stored array - updateDownload(cursor, arrayPos, now); - if (shouldScanFile(arrayPos) - && (!mediaScannerConnected() - || !scanFile(cursor, arrayPos))) { - mustScan = true; - keepService = true; - } - if (visibleNotification(arrayPos)) { - keepService = true; - } - long next = nextAction(arrayPos, now); - if (next == 0) { - keepService = true; - } else if (next > 0 && next < wakeUp) { - wakeUp = next; - } - ++arrayPos; - cursor.moveToNext(); - isAfterLast = cursor.isAfterLast(); - } else { - // This cursor entry didn't exist in the stored array - if (Constants.LOGVV) { - Log.v(Constants.TAG, "Array update: inserting " + - id + " @ " + arrayPos); - } - insertDownload(cursor, arrayPos, now); - if (shouldScanFile(arrayPos) - && (!mediaScannerConnected() - || !scanFile(cursor, arrayPos))) { - mustScan = true; - keepService = true; - } - if (visibleNotification(arrayPos)) { - keepService = true; - } - long next = nextAction(arrayPos, now); - if (next == 0) { - keepService = true; - } else if (next > 0 && next < wakeUp) { - wakeUp = next; - } - ++arrayPos; - cursor.moveToNext(); - isAfterLast = cursor.isAfterLast(); - } + long next = info.nextAction(now); + if (next == 0) { + keepService = true; + } else if (next > 0 && next < wakeUp) { + wakeUp = next; } } + } finally { + cursor.close(); + } + + for (Long id : idsNoLongerInDatabase) { + deleteDownload(id); } - mNotifier.updateNotification(mDownloads); + mNotifier.updateNotification(mDownloads.values()); if (mustScan) { - if (!mMediaScannerConnecting) { - Intent intent = new Intent(); - intent.setClassName("com.android.providers.media", - "com.android.providers.media.MediaScannerService"); - mMediaScannerConnecting = true; - bindService(intent, mMediaScannerConnection, BIND_AUTO_CREATE); - } + bindMediaScanner(); } else { mMediaScannerConnection.disconnectMediaScanner(); } + } + } - cursor.close(); + private void bindMediaScanner() { + if (!mMediaScannerConnecting) { + Intent intent = new Intent(); + intent.setClassName("com.android.providers.media", + "com.android.providers.media.MediaScannerService"); + mMediaScannerConnecting = true; + bindService(intent, mMediaScannerConnection, BIND_AUTO_CREATE); } } + + private void scheduleAlarm(long wakeUp) { + AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE); + if (alarms == null) { + Log.e(Constants.TAG, "couldn't get alarm manager"); + return; + } + + if (Constants.LOGV) { + Log.v(Constants.TAG, "scheduling retry in " + wakeUp + "ms"); + } + + Intent intent = new Intent(Constants.ACTION_RETRY); + intent.setClassName("com.android.providers.downloads", + DownloadReceiver.class.getName()); + alarms.set( + AlarmManager.RTC_WAKEUP, + mSystemFacade.currentTimeMillis() + wakeUp, + PendingIntent.getBroadcast(DownloadService.this, 0, intent, + PendingIntent.FLAG_ONE_SHOT)); + } } /** @@ -546,133 +430,40 @@ public class DownloadService extends Service { * Keeps a local copy of the info about a download, and initiates the * download if appropriate. */ - private void insertDownload(Cursor cursor, int arrayPos, long now) { - DownloadInfo info = new DownloadInfo(this, mSystemFacade, cursor); + private DownloadInfo insertDownload(DownloadInfo.Reader reader, long now) { + DownloadInfo info = reader.newDownloadInfo(this, mSystemFacade); + mDownloads.put(info.mId, info); if (Constants.LOGVV) { - Log.v(Constants.TAG, "Service adding new entry"); - Log.v(Constants.TAG, "ID : " + info.mId); - Log.v(Constants.TAG, "URI : " + ((info.mUri != null) ? "yes" : "no")); - Log.v(Constants.TAG, "NO_INTEG: " + info.mNoIntegrity); - Log.v(Constants.TAG, "HINT : " + info.mHint); - Log.v(Constants.TAG, "FILENAME: " + info.mFileName); - Log.v(Constants.TAG, "MIMETYPE: " + info.mMimeType); - Log.v(Constants.TAG, "DESTINAT: " + info.mDestination); - Log.v(Constants.TAG, "VISIBILI: " + info.mVisibility); - Log.v(Constants.TAG, "CONTROL : " + info.mControl); - Log.v(Constants.TAG, "STATUS : " + info.mStatus); - Log.v(Constants.TAG, "FAILED_C: " + info.mNumFailed); - Log.v(Constants.TAG, "RETRY_AF: " + info.mRetryAfter); - Log.v(Constants.TAG, "REDIRECT: " + info.mRedirectCount); - Log.v(Constants.TAG, "LAST_MOD: " + info.mLastMod); - Log.v(Constants.TAG, "PACKAGE : " + info.mPackage); - Log.v(Constants.TAG, "CLASS : " + info.mClass); - Log.v(Constants.TAG, "COOKIES : " + ((info.mCookies != null) ? "yes" : "no")); - Log.v(Constants.TAG, "AGENT : " + info.mUserAgent); - Log.v(Constants.TAG, "REFERER : " + ((info.mReferer != null) ? "yes" : "no")); - Log.v(Constants.TAG, "TOTAL : " + info.mTotalBytes); - Log.v(Constants.TAG, "CURRENT : " + info.mCurrentBytes); - Log.v(Constants.TAG, "ETAG : " + info.mETag); - Log.v(Constants.TAG, "SCANNED : " + info.mMediaScanned); - } - - mDownloads.add(arrayPos, info); - - if (info.mStatus == 0 - && (info.mDestination == Downloads.Impl.DESTINATION_EXTERNAL - || info.mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE) - && info.mMimeType != null - && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(info.mMimeType)) { - // Check to see if we are allowed to download this file. Only files - // that can be handled by the platform can be downloaded. - // special case DRM files, which we should always allow downloading. - Intent mimetypeIntent = new Intent(Intent.ACTION_VIEW); - - // We can provide data as either content: or file: URIs, - // so allow both. (I think it would be nice if we just did - // everything as content: URIs) - // Actually, right now the download manager's UId restrictions - // prevent use from using content: so it's got to be file: or - // nothing - - mimetypeIntent.setDataAndType(Uri.fromParts("file", "", null), info.mMimeType); - ResolveInfo ri = getPackageManager().resolveActivity(mimetypeIntent, - PackageManager.MATCH_DEFAULT_ONLY); - //Log.i(Constants.TAG, "*** QUERY " + mimetypeIntent + ": " + list); - - if (ri == null) { - Log.d(Constants.TAG, "no application to handle MIME type " + info.mMimeType); - info.mStatus = Downloads.Impl.STATUS_NOT_ACCEPTABLE; - - ContentValues values = new ContentValues(); - values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_NOT_ACCEPTABLE); - getContentResolver().update(info.getAllDownloadsUri(), values, null, null); - info.sendIntentIfRequested(); - return; - } + info.logVerboseInfo(); } if (info.isReadyToStart(now)) { info.start(now); } + + return info; } /** * Updates the local copy of the info about a download. */ - private void updateDownload(Cursor cursor, int arrayPos, long now) { - DownloadInfo info = mDownloads.get(arrayPos); - int statusColumn = cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_STATUS); - int failedColumn = cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS); - info.mId = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl._ID)); - info.mUri = stringFromCursor(info.mUri, cursor, Downloads.Impl.COLUMN_URI); - info.mNoIntegrity = cursor.getInt(cursor.getColumnIndexOrThrow( - Downloads.Impl.COLUMN_NO_INTEGRITY)) == 1; - info.mHint = stringFromCursor(info.mHint, cursor, Downloads.Impl.COLUMN_FILE_NAME_HINT); - info.mFileName = stringFromCursor(info.mFileName, cursor, Downloads.Impl._DATA); - info.mMimeType = stringFromCursor(info.mMimeType, cursor, Downloads.Impl.COLUMN_MIME_TYPE); - info.mDestination = cursor.getInt(cursor.getColumnIndexOrThrow( - Downloads.Impl.COLUMN_DESTINATION)); - int newVisibility = cursor.getInt(cursor.getColumnIndexOrThrow( - Downloads.Impl.COLUMN_VISIBILITY)); - if (info.mVisibility == Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED - && newVisibility != Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED - && Downloads.Impl.isStatusCompleted(info.mStatus)) { - mSystemFacade.cancelNotification(info.mId); - } - info.mVisibility = newVisibility; - synchronized (info) { - info.mControl = cursor.getInt(cursor.getColumnIndexOrThrow( - Downloads.Impl.COLUMN_CONTROL)); - } - int newStatus = cursor.getInt(statusColumn); - if (!Downloads.Impl.isStatusCompleted(info.mStatus) && - Downloads.Impl.isStatusCompleted(newStatus)) { + private void updateDownload(DownloadInfo.Reader reader, DownloadInfo info, long now) { + int oldVisibility = info.mVisibility; + int oldStatus = info.mStatus; + + reader.updateFromDatabase(info); + + boolean lostVisibility = + oldVisibility == Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED + && info.mVisibility != Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED + && Downloads.Impl.isStatusCompleted(info.mStatus); + boolean justCompleted = + !Downloads.Impl.isStatusCompleted(oldStatus) + && Downloads.Impl.isStatusCompleted(info.mStatus); + if (lostVisibility || justCompleted) { mSystemFacade.cancelNotification(info.mId); } - info.mStatus = newStatus; - info.mNumFailed = cursor.getInt(failedColumn); - int retryRedirect = - cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER_X_REDIRECT_COUNT)); - info.mRetryAfter = retryRedirect & 0xfffffff; - info.mRedirectCount = retryRedirect >> 28; - info.mLastMod = cursor.getLong(cursor.getColumnIndexOrThrow( - Downloads.Impl.COLUMN_LAST_MODIFICATION)); - info.mPackage = stringFromCursor( - info.mPackage, cursor, Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE); - info.mClass = stringFromCursor( - info.mClass, cursor, Downloads.Impl.COLUMN_NOTIFICATION_CLASS); - info.mCookies = stringFromCursor(info.mCookies, cursor, Downloads.Impl.COLUMN_COOKIE_DATA); - info.mUserAgent = stringFromCursor( - info.mUserAgent, cursor, Downloads.Impl.COLUMN_USER_AGENT); - info.mReferer = stringFromCursor(info.mReferer, cursor, Downloads.Impl.COLUMN_REFERER); - info.mTotalBytes = cursor.getInt(cursor.getColumnIndexOrThrow( - Downloads.Impl.COLUMN_TOTAL_BYTES)); - info.mCurrentBytes = cursor.getInt(cursor.getColumnIndexOrThrow( - Downloads.Impl.COLUMN_CURRENT_BYTES)); - info.mETag = stringFromCursor(info.mETag, cursor, Constants.ETAG); - info.mMediaScanned = - cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1; if (info.isReadyToRestart(now)) { info.start(now); @@ -680,128 +471,48 @@ public class DownloadService extends Service { } /** - * Returns a String that holds the current value of the column, - * optimizing for the case where the value hasn't changed. - */ - private String stringFromCursor(String old, Cursor cursor, String column) { - int index = cursor.getColumnIndexOrThrow(column); - if (old == null) { - return cursor.getString(index); - } - if (mNewChars == null) { - mNewChars = new CharArrayBuffer(128); - } - cursor.copyStringToBuffer(index, mNewChars); - int length = mNewChars.sizeCopied; - if (length != old.length()) { - return cursor.getString(index); - } - if (oldChars == null || oldChars.sizeCopied < length) { - oldChars = new CharArrayBuffer(length); - } - char[] oldArray = oldChars.data; - char[] newArray = mNewChars.data; - old.getChars(0, length, oldArray, 0); - for (int i = length - 1; i >= 0; --i) { - if (oldArray[i] != newArray[i]) { - return new String(newArray, 0, length); - } - } - return old; - } - - /** * Removes the local copy of the info about a download. */ - private void deleteDownload(int arrayPos) { - DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos); + private void deleteDownload(long id) { + DownloadInfo info = mDownloads.get(id); + if (info.shouldScanFile()) { + scanFile(info, false); + } if (info.mStatus == Downloads.Impl.STATUS_RUNNING) { info.mStatus = Downloads.Impl.STATUS_CANCELED; - } else if (info.mDestination != Downloads.Impl.DESTINATION_EXTERNAL - && info.mFileName != null) { + } + if (info.mDestination != Downloads.Impl.DESTINATION_EXTERNAL && info.mFileName != null) { new File(info.mFileName).delete(); } mSystemFacade.cancelNotification(info.mId); - - mDownloads.remove(arrayPos); - } - - /** - * Returns the amount of time (as measured from the "now" parameter) - * at which a download will be active. - * 0 = immediately - service should stick around to handle this download. - * -1 = never - service can go away without ever waking up. - * positive value - service must wake up in the future, as specified in ms from "now" - */ - private long nextAction(int arrayPos, long now) { - DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos); - if (Downloads.Impl.isStatusCompleted(info.mStatus)) { - return -1; - } - if (info.mStatus != Downloads.Impl.STATUS_RUNNING_PAUSED) { - return 0; - } - if (info.mNumFailed == 0) { - return 0; - } - long when = info.restartTime(); - if (when <= now) { - return 0; - } - return when - now; - } - - /** - * Returns whether there's a visible notification for this download - */ - private boolean visibleNotification(int arrayPos) { - DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos); - return info.hasCompletionNotification(); - } - - /** - * Returns whether a file should be scanned - */ - private boolean shouldScanFile(int arrayPos) { - DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos); - return !info.mMediaScanned - && info.mDestination == Downloads.Impl.DESTINATION_EXTERNAL - && Downloads.Impl.isStatusSuccess(info.mStatus) - && !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(info.mMimeType); - } - - /** - * Returns whether we have a live connection to the Media Scanner - */ - private boolean mediaScannerConnected() { - return mMediaScannerService != null; + mDownloads.remove(info.mId); } /** * Attempts to scan the file if necessary. - * Returns true if the file has been properly scanned. + * @return true if the file has been properly scanned. */ - private boolean scanFile(Cursor cursor, int arrayPos) { - DownloadInfo info = mDownloads.get(arrayPos); + private boolean scanFile(DownloadInfo info, boolean updateDatabase) { synchronized (this) { - if (mMediaScannerService != null) { - try { - if (Constants.LOGV) { - Log.v(Constants.TAG, "Scanning file " + info.mFileName); - } - mMediaScannerService.scanFile(info.mFileName, info.mMimeType); - if (cursor != null) { - ContentValues values = new ContentValues(); - values.put(Constants.MEDIA_SCANNED, 1); - getContentResolver().update(info.getAllDownloadsUri(), values, null, null); - } - return true; - } catch (RemoteException e) { - Log.d(Constants.TAG, "Failed to scan file " + info.mFileName); + if (mMediaScannerService == null) { + return false; + } + try { + if (Constants.LOGV) { + Log.v(Constants.TAG, "Scanning file " + info.mFileName); + } + mMediaScannerService.scanFile(info.mFileName, info.mMimeType); + if (updateDatabase) { + ContentValues values = new ContentValues(); + values.put(Constants.MEDIA_SCANNED, 1); + getContentResolver().update(info.getAllDownloadsUri(), values, null, null); } + return true; + } catch (RemoteException e) { + Log.d(Constants.TAG, "Failed to scan file " + info.mFileName); + return false; } } - return false; } } diff --git a/src/com/android/providers/downloads/RealSystemFacade.java b/src/com/android/providers/downloads/RealSystemFacade.java index 710da10d..421fc2be 100644 --- a/src/com/android/providers/downloads/RealSystemFacade.java +++ b/src/com/android/providers/downloads/RealSystemFacade.java @@ -81,13 +81,18 @@ class RealSystemFacade implements SystemFacade { } @Override - public void postNotification(int id, Notification notification) { - mNotificationManager.notify(id, notification); + public void postNotification(long id, Notification notification) { + /** + * TODO: The system notification manager takes ints, not longs, as IDs, but the download + * manager uses IDs take straight from the database, which are longs. This will have to be + * dealt with at some point. + */ + mNotificationManager.notify((int) id, notification); } @Override - public void cancelNotification(int id) { - mNotificationManager.cancel(id); + public void cancelNotification(long id) { + mNotificationManager.cancel((int) id); } @Override diff --git a/src/com/android/providers/downloads/SystemFacade.java b/src/com/android/providers/downloads/SystemFacade.java index c1941692..50624c3a 100644 --- a/src/com/android/providers/downloads/SystemFacade.java +++ b/src/com/android/providers/downloads/SystemFacade.java @@ -42,12 +42,12 @@ interface SystemFacade { /** * Post a system notification to the NotificationManager. */ - public void postNotification(int id, Notification notification); + public void postNotification(long id, Notification notification); /** * Cancel a system notification. */ - public void cancelNotification(int id); + public void cancelNotification(long id); /** * Cancel all system notifications. diff --git a/tests/src/com/android/providers/downloads/FakeSystemFacade.java b/tests/src/com/android/providers/downloads/FakeSystemFacade.java index 40b2a900..d80bd4ad 100644 --- a/tests/src/com/android/providers/downloads/FakeSystemFacade.java +++ b/tests/src/com/android/providers/downloads/FakeSystemFacade.java @@ -19,7 +19,7 @@ public class FakeSystemFacade implements SystemFacade { boolean mIsRoaming = false; Long mMaxBytesOverMobile = null; List<Intent> mBroadcastsSent = new ArrayList<Intent>(); - Map<Integer,Notification> mActiveNotifications = new HashMap<Integer,Notification>(); + Map<Long,Notification> mActiveNotifications = new HashMap<Long,Notification>(); List<Notification> mCanceledNotifications = new ArrayList<Notification>(); Queue<Thread> mStartedThreads = new LinkedList<Thread>(); @@ -54,7 +54,7 @@ public class FakeSystemFacade implements SystemFacade { } @Override - public void postNotification(int id, Notification notification) { + public void postNotification(long id, Notification notification) { if (notification == null) { throw new AssertionFailedError("Posting null notification"); } @@ -62,7 +62,7 @@ public class FakeSystemFacade implements SystemFacade { } @Override - public void cancelNotification(int id) { + public void cancelNotification(long id) { Notification notification = mActiveNotifications.remove(id); if (notification != null) { mCanceledNotifications.add(notification); @@ -71,7 +71,7 @@ public class FakeSystemFacade implements SystemFacade { @Override public void cancelAllNotifications() { - for (int id : mActiveNotifications.keySet()) { + for (long id : mActiveNotifications.keySet()) { cancelNotification(id); } } diff --git a/ui/src/com/android/providers/downloads/ui/DownloadList.java b/ui/src/com/android/providers/downloads/ui/DownloadList.java index fce2f162..71e17233 100644 --- a/ui/src/com/android/providers/downloads/ui/DownloadList.java +++ b/ui/src/com/android/providers/downloads/ui/DownloadList.java @@ -19,6 +19,7 @@ package com.android.providers.downloads.ui; import android.app.Activity; import android.app.AlertDialog; import android.content.ActivityNotFoundException; +import android.content.ContentUris; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; @@ -365,7 +366,7 @@ public class DownloadList extends Activity Intent intent = new Intent("android.intent.action.DOWNLOAD_LIST"); intent.setClassName("com.android.providers.downloads", "com.android.providers.downloads.DownloadReceiver"); - intent.setData(Uri.parse(Downloads.Impl.CONTENT_URI + "/" + id)); + intent.setData(ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id)); intent.putExtra("multiple", false); sendBroadcast(intent); } |