From 5224c6fbf20b4803a580ef449ab87ebfbbfedb78 Mon Sep 17 00:00:00 2001 From: Steve Howard Date: Wed, 14 Jul 2010 11:30:59 -0700 Subject: Support for custom HTTP headers on download requests Provider changes: * new many-to-one DB table holding headers for each download. since there was no real migration logic in DownloadProvider, I implemented some. * DownloadProvider.insert() reads request headers out of the ContentValues and puts them into the new table * DownloadProvider.query() supports a new URI form, download/#/headers, to fetch the headers associated with a download * DownloadProvider.delete() removes request headers from this table Service changes: * made DownloadInfo store request headers upon initialization. While I was at it, I refactored the initialization logic into DownloadInfo to get rid of the massive 24-parameter constructor. The right next step would be to move the update logic into DownloadInfo and merge it with the initialization logic; however, I realized that headers don't need to be updatable, and in the future, we won't need the update logic at all, so i didn't bother touching the update code. * made DownloadThread read headers from the DownloadInfo and include them in the request; merged the custom Cookie and Referer logic into this logic Also added a couple new test cases for this stuff. Change-Id: I421ce1f0a694e815f2e099795182393650fcb3ff --- .../android/providers/downloads/DownloadInfo.java | 109 +++++--- .../providers/downloads/DownloadProvider.java | 275 ++++++++++++++------- .../providers/downloads/DownloadService.java | 36 +-- .../providers/downloads/DownloadThread.java | 28 ++- .../downloads/PublicApiFunctionalTest.java | 22 ++ 5 files changed, 295 insertions(+), 175 deletions(-) diff --git a/src/com/android/providers/downloads/DownloadInfo.java b/src/com/android/providers/downloads/DownloadInfo.java index 81895439..1ae10ce1 100644 --- a/src/com/android/providers/downloads/DownloadInfo.java +++ b/src/com/android/providers/downloads/DownloadInfo.java @@ -16,11 +16,17 @@ package com.android.providers.downloads; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.database.Cursor; import android.net.Uri; import android.provider.Downloads; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + /** * Stores information about an individual download. */ @@ -53,39 +59,78 @@ public class DownloadInfo { public int mFuzz; public volatile boolean mHasActiveThread; + private Map mRequestHeaders = new HashMap(); + + public DownloadInfo(ContentResolver resolver, Cursor cursor) { + 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; + mFuzz = Helpers.sRandom.nextInt(1001); + + readRequestHeaders(resolver, mId); + } + + private void readRequestHeaders(ContentResolver resolver, long downloadId) { + Uri headerUri = Downloads.Impl.CONTENT_URI.buildUpon() + .appendPath(Long.toString(downloadId)) + .appendPath(Downloads.Impl.RequestHeaders.URI_SEGMENT).build(); + Cursor cursor = resolver.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 DownloadInfo(int id, String uri, boolean noIntegrity, - String hint, String fileName, - String mimeType, int destination, int visibility, int control, - int status, int numFailed, int retryAfter, int redirectCount, long lastMod, - String pckg, String clazz, String extras, String cookies, - String userAgent, String referer, int totalBytes, int currentBytes, String eTag, - boolean mediaScanned) { - mId = id; - mUri = uri; - mNoIntegrity = noIntegrity; - mHint = hint; - mFileName = fileName; - mMimeType = mimeType; - mDestination = destination; - mVisibility = visibility; - mControl = control; - mStatus = status; - mNumFailed = numFailed; - mRetryAfter = retryAfter; - mRedirectCount = redirectCount; - mLastMod = lastMod; - mPackage = pckg; - mClass = clazz; - mExtras = extras; - mCookies = cookies; - mUserAgent = userAgent; - mReferer = referer; - mTotalBytes = totalBytes; - mCurrentBytes = currentBytes; - mETag = eTag; - mMediaScanned = mediaScanned; - mFuzz = Helpers.sRandom.nextInt(1001); + public Map getHeaders() { + return Collections.unmodifiableMap(mRequestHeaders); } public void sendIntentIfRequested(Uri contentUri, Context context) { diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java index 7070f31c..186b01f1 100644 --- a/src/com/android/providers/downloads/DownloadProvider.java +++ b/src/com/android/providers/downloads/DownloadProvider.java @@ -16,8 +16,6 @@ package com.android.providers.downloads; -import com.google.common.annotations.VisibleForTesting; - import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; @@ -42,9 +40,12 @@ import android.provider.Downloads; import android.util.Config; import android.util.Log; +import com.google.common.annotations.VisibleForTesting; + import java.io.File; import java.io.FileNotFoundException; import java.util.HashSet; +import java.util.Map; /** @@ -55,11 +56,7 @@ public final class DownloadProvider extends ContentProvider { /** Database filename */ private static final String DB_NAME = "downloads.db"; /** Current database version */ - private static final int DB_VERSION = 100; - /** Database version from which upgrading is a nop */ - private static final int DB_VERSION_NOP_UPGRADE_FROM = 31; - /** Database version to which upgrading is a nop */ - private static final int DB_VERSION_NOP_UPGRADE_TO = 100; + private static final int DB_VERSION = 101; /** Name of table in the database */ private static final String DB_TABLE = "downloads"; @@ -74,9 +71,13 @@ public final class DownloadProvider extends ContentProvider { private static final int DOWNLOADS = 1; /** URI matcher constant for the URI of an individual download */ private static final int DOWNLOADS_ID = 2; + /** URI matcher constant for the URI of a download's request headers */ + private static final int REQUEST_HEADERS_URI = 3; static { sURIMatcher.addURI("downloads", "download", DOWNLOADS); sURIMatcher.addURI("downloads", "download/#", DOWNLOADS_ID); + sURIMatcher.addURI("downloads", "download/#/" + Downloads.Impl.RequestHeaders.URI_SEGMENT, + REQUEST_HEADERS_URI); } private static final String[] sAppReadableColumnsArray = new String[] { @@ -122,7 +123,6 @@ public final class DownloadProvider extends ContentProvider { * an updated version of the database. */ private final class DatabaseHelper extends SQLiteOpenHelper { - public DatabaseHelper(final Context context) { super(context, DB_NAME, null, DB_VERSION); } @@ -135,40 +135,109 @@ public final class DownloadProvider extends ContentProvider { if (Constants.LOGVV) { Log.v(Constants.TAG, "populating new database"); } - createTable(db); + onUpgrade(db, 0, DB_VERSION); } - /* (not a javadoc comment) - * Checks data integrity when opening the database. - */ - /* - * @Override - * public void onOpen(final SQLiteDatabase db) { - * super.onOpen(db); - * } - */ - /** * Updates the database format when a content provider is used * with a database that was created with a different format. + * + * Note: to support downgrades, creating a table should always drop it first if it already + * exists. */ - // Note: technically, this could also be a downgrade, so if we want - // to gracefully handle upgrades we should be careful about - // what to do on downgrades. @Override public void onUpgrade(final SQLiteDatabase db, int oldV, final int newV) { - if (oldV == DB_VERSION_NOP_UPGRADE_FROM) { - if (newV == DB_VERSION_NOP_UPGRADE_TO) { // that's a no-op upgrade. - return; - } - // NOP_FROM and NOP_TO are identical, just in different codelines. Upgrading - // from NOP_FROM is the same as upgrading from NOP_TO. - oldV = DB_VERSION_NOP_UPGRADE_TO; + if (oldV == 31) { + // 31 and 100 are identical, just in different codelines. Upgrading from 31 is the + // same as upgrading from 100. + oldV = 100; + } else if (oldV < 100) { + // no logic to upgrade from these older version, just recreate the DB + Log.i(Constants.TAG, "Upgrading downloads database from version " + oldV + + " to version " + newV + ", which will destroy all old data"); + oldV = 99; + } else if (oldV > newV) { + // user must have downgraded software; we have no way to know how to downgrade the + // DB, so just recreate it + Log.i(Constants.TAG, "Downgrading downloads database from version " + oldV + + " (current version is " + newV + "), destroying all old data"); + oldV = 99; + } + + for (int version = oldV + 1; version <= newV; version++) { + upgradeTo(db, version); } - Log.i(Constants.TAG, "Upgrading downloads database from version " + oldV + " to " + newV - + ", which will destroy all old data"); - dropTable(db); - createTable(db); + } + + /** + * Upgrade database from (version - 1) to version. + */ + private void upgradeTo(SQLiteDatabase db, int version) { + switch (version) { + case 100: + createDownloadsTable(db); + break; + + case 101: + createHeadersTable(db); + break; + + default: + throw new IllegalStateException("Don't know how to upgrade to " + version); + } + } + + /** + * Creates the table that'll hold the download information. + */ + private void createDownloadsTable(SQLiteDatabase db) { + try { + db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE); + db.execSQL("CREATE TABLE " + DB_TABLE + "(" + + Downloads.Impl._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + + Downloads.Impl.COLUMN_URI + " TEXT, " + + Constants.RETRY_AFTER_X_REDIRECT_COUNT + " INTEGER, " + + Downloads.Impl.COLUMN_APP_DATA + " TEXT, " + + Downloads.Impl.COLUMN_NO_INTEGRITY + " BOOLEAN, " + + Downloads.Impl.COLUMN_FILE_NAME_HINT + " TEXT, " + + Constants.OTA_UPDATE + " BOOLEAN, " + + Downloads.Impl._DATA + " TEXT, " + + Downloads.Impl.COLUMN_MIME_TYPE + " TEXT, " + + Downloads.Impl.COLUMN_DESTINATION + " INTEGER, " + + Constants.NO_SYSTEM_FILES + " BOOLEAN, " + + Downloads.Impl.COLUMN_VISIBILITY + " INTEGER, " + + Downloads.Impl.COLUMN_CONTROL + " INTEGER, " + + Downloads.Impl.COLUMN_STATUS + " INTEGER, " + + Constants.FAILED_CONNECTIONS + " INTEGER, " + + Downloads.Impl.COLUMN_LAST_MODIFICATION + " BIGINT, " + + Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE + " TEXT, " + + Downloads.Impl.COLUMN_NOTIFICATION_CLASS + " TEXT, " + + Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS + " TEXT, " + + Downloads.Impl.COLUMN_COOKIE_DATA + " TEXT, " + + Downloads.Impl.COLUMN_USER_AGENT + " TEXT, " + + Downloads.Impl.COLUMN_REFERER + " TEXT, " + + Downloads.Impl.COLUMN_TOTAL_BYTES + " INTEGER, " + + Downloads.Impl.COLUMN_CURRENT_BYTES + " INTEGER, " + + Constants.ETAG + " TEXT, " + + Constants.UID + " INTEGER, " + + Downloads.Impl.COLUMN_OTHER_UID + " INTEGER, " + + Downloads.Impl.COLUMN_TITLE + " TEXT, " + + Downloads.Impl.COLUMN_DESCRIPTION + " TEXT, " + + Constants.MEDIA_SCANNED + " BOOLEAN);"); + } catch (SQLException ex) { + Log.e(Constants.TAG, "couldn't create table in downloads database"); + throw ex; + } + } + + private void createHeadersTable(SQLiteDatabase db) { + db.execSQL("DROP TABLE IF EXISTS " + Downloads.Impl.RequestHeaders.HEADERS_DB_TABLE); + db.execSQL("CREATE TABLE " + Downloads.Impl.RequestHeaders.HEADERS_DB_TABLE + "(" + + "id INTEGER PRIMARY KEY AUTOINCREMENT," + + Downloads.Impl.RequestHeaders.COLUMN_DOWNLOAD_ID + " INTEGER NOT NULL," + + Downloads.Impl.RequestHeaders.COLUMN_HEADER + " TEXT NOT NULL," + + Downloads.Impl.RequestHeaders.COLUMN_VALUE + " TEXT NOT NULL" + + ");"); } } @@ -223,60 +292,6 @@ public final class DownloadProvider extends ContentProvider { } } - /** - * Creates the table that'll hold the download information. - */ - private void createTable(SQLiteDatabase db) { - try { - db.execSQL("CREATE TABLE " + DB_TABLE + "(" + - Downloads.Impl._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + - Downloads.Impl.COLUMN_URI + " TEXT, " + - Constants.RETRY_AFTER_X_REDIRECT_COUNT + " INTEGER, " + - Downloads.Impl.COLUMN_APP_DATA + " TEXT, " + - Downloads.Impl.COLUMN_NO_INTEGRITY + " BOOLEAN, " + - Downloads.Impl.COLUMN_FILE_NAME_HINT + " TEXT, " + - Constants.OTA_UPDATE + " BOOLEAN, " + - Downloads.Impl._DATA + " TEXT, " + - Downloads.Impl.COLUMN_MIME_TYPE + " TEXT, " + - Downloads.Impl.COLUMN_DESTINATION + " INTEGER, " + - Constants.NO_SYSTEM_FILES + " BOOLEAN, " + - Downloads.Impl.COLUMN_VISIBILITY + " INTEGER, " + - Downloads.Impl.COLUMN_CONTROL + " INTEGER, " + - Downloads.Impl.COLUMN_STATUS + " INTEGER, " + - Constants.FAILED_CONNECTIONS + " INTEGER, " + - Downloads.Impl.COLUMN_LAST_MODIFICATION + " BIGINT, " + - Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE + " TEXT, " + - Downloads.Impl.COLUMN_NOTIFICATION_CLASS + " TEXT, " + - Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS + " TEXT, " + - Downloads.Impl.COLUMN_COOKIE_DATA + " TEXT, " + - Downloads.Impl.COLUMN_USER_AGENT + " TEXT, " + - Downloads.Impl.COLUMN_REFERER + " TEXT, " + - Downloads.Impl.COLUMN_TOTAL_BYTES + " INTEGER, " + - Downloads.Impl.COLUMN_CURRENT_BYTES + " INTEGER, " + - Constants.ETAG + " TEXT, " + - Constants.UID + " INTEGER, " + - Downloads.Impl.COLUMN_OTHER_UID + " INTEGER, " + - Downloads.Impl.COLUMN_TITLE + " TEXT, " + - Downloads.Impl.COLUMN_DESCRIPTION + " TEXT, " + - Constants.MEDIA_SCANNED + " BOOLEAN);"); - } catch (SQLException ex) { - Log.e(Constants.TAG, "couldn't create table in downloads database"); - throw ex; - } - } - - /** - * Deletes the table that holds the download information. - */ - private void dropTable(SQLiteDatabase db) { - try { - db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE); - } catch (SQLException ex) { - Log.e(Constants.TAG, "couldn't drop table in downloads database"); - throw ex; - } - } - /** * Inserts a row in the database */ @@ -373,6 +388,7 @@ public final class DownloadProvider extends ContentProvider { context.startService(new Intent(context, DownloadService.class)); long rowID = db.insert(DB_TABLE, null, filteredValues); + insertRequestHeaders(db, rowID, values); Uri ret = null; @@ -413,10 +429,16 @@ public final class DownloadProvider extends ContentProvider { case DOWNLOADS_ID: { qb.setTables(DB_TABLE); qb.appendWhere(Downloads.Impl._ID + "="); - qb.appendWhere(uri.getPathSegments().get(1)); + qb.appendWhere(getDownloadIdFromUri(uri)); emptyWhere = false; break; } + case REQUEST_HEADERS_URI: + if (projection != null || selection != null || sort != null) { + throw new UnsupportedOperationException("Request header queries do not support " + + "projections, selections or sorting"); + } + return queryRequestHeaders(db, uri); default: { if (Constants.LOGV) { Log.v(Constants.TAG, "querying unknown URI: " + uri); @@ -425,11 +447,7 @@ public final class DownloadProvider extends ContentProvider { } } - int callingUid = Binder.getCallingUid(); - if (Binder.getCallingPid() != Process.myPid() && - callingUid != mSystemUid && - callingUid != mDefContainerUid && - Process.supportsProcesses()) { + if (shouldRestrictVisibility()) { boolean canSeeAllExternal; if (projection == null) { projection = sAppReadableColumnsArray; @@ -530,6 +548,72 @@ public final class DownloadProvider extends ContentProvider { return ret; } + private String getDownloadIdFromUri(final Uri uri) { + return uri.getPathSegments().get(1); + } + + /** + * Insert request headers for a download into the DB. + */ + private void insertRequestHeaders(SQLiteDatabase db, long downloadId, ContentValues values) { + ContentValues rowValues = new ContentValues(); + rowValues.put(Downloads.Impl.RequestHeaders.COLUMN_DOWNLOAD_ID, downloadId); + for (Map.Entry entry : values.valueSet()) { + String key = entry.getKey(); + if (key.startsWith(Downloads.Impl.RequestHeaders.INSERT_KEY_PREFIX)) { + String headerLine = entry.getValue().toString(); + if (!headerLine.contains(":")) { + throw new IllegalArgumentException("Invalid HTTP header line: " + headerLine); + } + String[] parts = headerLine.split(":", 2); + rowValues.put(Downloads.Impl.RequestHeaders.COLUMN_HEADER, parts[0].trim()); + rowValues.put(Downloads.Impl.RequestHeaders.COLUMN_VALUE, parts[1].trim()); + db.insert(Downloads.Impl.RequestHeaders.HEADERS_DB_TABLE, null, rowValues); + } + } + } + + /** + * Handle a query for the custom request headers registered for a download. + */ + private Cursor queryRequestHeaders(SQLiteDatabase db, Uri uri) { + String where = Downloads.Impl.RequestHeaders.COLUMN_DOWNLOAD_ID + "=" + + getDownloadIdFromUri(uri); + String[] projection = new String[] {Downloads.Impl.RequestHeaders.COLUMN_HEADER, + Downloads.Impl.RequestHeaders.COLUMN_VALUE}; + Cursor cursor = db.query(Downloads.Impl.RequestHeaders.HEADERS_DB_TABLE, projection, where, + null, null, null, null); + return new ReadOnlyCursorWrapper(cursor); + } + + /** + * Delete request headers for downloads matching the given query. + */ + private void deleteRequestHeaders(SQLiteDatabase db, String where, String[] whereArgs) { + String[] projection = new String[] {Downloads.Impl._ID}; + Cursor cursor = db.query(DB_TABLE, projection , where, whereArgs, null, null, null, null); + try { + for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { + long id = cursor.getLong(0); + String idWhere = Downloads.Impl.RequestHeaders.COLUMN_DOWNLOAD_ID + "=" + id; + db.delete(Downloads.Impl.RequestHeaders.HEADERS_DB_TABLE, idWhere, null); + } + } finally { + cursor.close(); + } + } + + /** + * @return true if we should restrict this call to viewing only its own downloads + */ + private boolean shouldRestrictVisibility() { + int callingUid = Binder.getCallingUid(); + return Binder.getCallingPid() != Process.myPid() && + callingUid != mSystemUid && + callingUid != mDefContainerUid && + Process.supportsProcesses(); + } + /** * Updates a row in the database */ @@ -586,7 +670,7 @@ public final class DownloadProvider extends ContentProvider { myWhere = ""; } if (match == DOWNLOADS_ID) { - String segment = uri.getPathSegments().get(1); + String segment = getDownloadIdFromUri(uri); rowId = Long.parseLong(segment); myWhere += " ( " + Downloads.Impl._ID + " = " + rowId + " ) "; } @@ -645,7 +729,7 @@ public final class DownloadProvider extends ContentProvider { myWhere = ""; } if (match == DOWNLOADS_ID) { - String segment = uri.getPathSegments().get(1); + String segment = getDownloadIdFromUri(uri); long rowId = Long.parseLong(segment); myWhere += " ( " + Downloads.Impl._ID + " = " + rowId + " ) "; } @@ -657,6 +741,7 @@ public final class DownloadProvider extends ContentProvider { + Downloads.Impl.COLUMN_OTHER_UID + "=" + Binder.getCallingUid() + " )"; } + deleteRequestHeaders(db, where, whereArgs); count = db.delete(DB_TABLE, myWhere, whereArgs); break; } diff --git a/src/com/android/providers/downloads/DownloadService.java b/src/com/android/providers/downloads/DownloadService.java index f3bc9586..2e713fbf 100644 --- a/src/com/android/providers/downloads/DownloadService.java +++ b/src/com/android/providers/downloads/DownloadService.java @@ -555,41 +555,7 @@ public class DownloadService extends Service { private void insertDownload( Cursor cursor, int arrayPos, boolean networkAvailable, boolean networkRoaming, long now) { - int statusColumn = cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_STATUS); - int failedColumn = cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS); - int retryRedirect = - cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER_X_REDIRECT_COUNT)); - DownloadInfo info = new DownloadInfo( - cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl._ID)), - cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_URI)), - cursor.getInt(cursor.getColumnIndexOrThrow( - Downloads.Impl.COLUMN_NO_INTEGRITY)) == 1, - cursor.getString(cursor.getColumnIndexOrThrow( - Downloads.Impl.COLUMN_FILE_NAME_HINT)), - cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl._DATA)), - cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_MIME_TYPE)), - cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_DESTINATION)), - cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_VISIBILITY)), - cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_CONTROL)), - cursor.getInt(statusColumn), - cursor.getInt(failedColumn), - retryRedirect & 0xfffffff, - retryRedirect >> 28, - cursor.getLong(cursor.getColumnIndexOrThrow( - Downloads.Impl.COLUMN_LAST_MODIFICATION)), - cursor.getString(cursor.getColumnIndexOrThrow( - Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE)), - cursor.getString(cursor.getColumnIndexOrThrow( - Downloads.Impl.COLUMN_NOTIFICATION_CLASS)), - cursor.getString(cursor.getColumnIndexOrThrow( - Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS)), - cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_COOKIE_DATA)), - cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_USER_AGENT)), - cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_REFERER)), - cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_TOTAL_BYTES)), - cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_CURRENT_BYTES)), - cursor.getString(cursor.getColumnIndexOrThrow(Constants.ETAG)), - cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1); + DownloadInfo info = new DownloadInfo(getContentResolver(), cursor); if (Constants.LOGVV) { Log.v(Constants.TAG, "Service adding new entry"); diff --git a/src/com/android/providers/downloads/DownloadThread.java b/src/com/android/providers/downloads/DownloadThread.java index f1296b8f..8188eaae 100644 --- a/src/com/android/providers/downloads/DownloadThread.java +++ b/src/com/android/providers/downloads/DownloadThread.java @@ -16,12 +16,6 @@ package com.android.providers.downloads; -import org.apache.http.Header; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.HttpClient; -import org.apache.http.entity.StringEntity; - import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; @@ -37,9 +31,11 @@ import android.provider.DrmStore; import android.util.Config; import android.util.Log; +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; import java.io.File; -import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; @@ -48,6 +44,7 @@ import java.io.SyncFailedException; import java.net.URI; import java.net.URISyntaxException; import java.util.Locale; +import java.util.Map; /** * Runs an actual download @@ -189,12 +186,8 @@ http_request_loop: Log.v(Constants.TAG, "initiating download for " + mInfo.mUri); } - if (mInfo.mCookies != null) { - request.addHeader("Cookie", mInfo.mCookies); - } - if (mInfo.mReferer != null) { - request.addHeader("Referer", mInfo.mReferer); - } + addRequestHeaders(request); + if (continuingDownload) { if (headerETag != null) { request.addHeader("If-Match", headerETag); @@ -727,6 +720,15 @@ http_request_loop: } } + /** + * Add custom headers for this download to the HTTP request. + */ + private void addRequestHeaders(HttpGet request) { + for (Map.Entry header : mInfo.getHeaders().entrySet()) { + request.addHeader(header.getKey(), header.getValue()); + } + } + /** * Stores information about the completed download, and notifies the initiating application. */ diff --git a/tests/src/com/android/providers/downloads/PublicApiFunctionalTest.java b/tests/src/com/android/providers/downloads/PublicApiFunctionalTest.java index e3b278bc..e9195609 100644 --- a/tests/src/com/android/providers/downloads/PublicApiFunctionalTest.java +++ b/tests/src/com/android/providers/downloads/PublicApiFunctionalTest.java @@ -20,6 +20,7 @@ import android.database.Cursor; import android.net.DownloadManager; import android.net.Uri; import android.os.Environment; +import android.util.Log; import tests.http.RecordedRequest; import java.io.File; @@ -271,6 +272,27 @@ public class PublicApiFunctionalTest extends AbstractDownloadManagerFunctionalTe } } + public void testRequestHeaders() throws Exception { + enqueueEmptyResponse(HTTP_OK); + Download download = enqueueRequest(getRequest().setRequestHeader("Header1", "value1") + .setRequestHeader("Header2", "value2")); + RecordedRequest request = download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL); + + assertTrue(request.getHeaders().contains("Header1: value1")); + assertTrue(request.getHeaders().contains("Header2: value2")); + } + + public void testDelete() throws Exception { + Download download = enqueueRequest(getRequest().setRequestHeader("header", "value")); + mManager.remove(download.mId); + Cursor cursor = mManager.query(new DownloadManager.Query()); + try { + assertEquals(0, cursor.getCount()); + } finally { + cursor.close(); + } + } + private DownloadManager.Request getRequest() throws MalformedURLException { return getRequest(getServerUri(REQUEST_PATH)); } -- cgit v1.2.3