summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/com/android/providers/downloads/DownloadInfo.java264
-rw-r--r--src/com/android/providers/downloads/DownloadNotification.java15
-rw-r--r--src/com/android/providers/downloads/DownloadProvider.java5
-rw-r--r--src/com/android/providers/downloads/DownloadReceiver.java265
-rw-r--r--src/com/android/providers/downloads/DownloadService.java545
-rw-r--r--src/com/android/providers/downloads/RealSystemFacade.java13
-rw-r--r--src/com/android/providers/downloads/SystemFacade.java4
-rw-r--r--tests/src/com/android/providers/downloads/FakeSystemFacade.java8
-rw-r--r--ui/src/com/android/providers/downloads/ui/DownloadList.java3
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 edbfb783..df7ca71d 100644
--- a/src/com/android/providers/downloads/DownloadProvider.java
+++ b/src/com/android/providers/downloads/DownloadProvider.java
@@ -827,9 +827,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);
}