summaryrefslogtreecommitdiffstats
path: root/src/com/android/providers/downloads/DownloadProvider.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/providers/downloads/DownloadProvider.java')
-rw-r--r--src/com/android/providers/downloads/DownloadProvider.java731
1 files changed, 731 insertions, 0 deletions
diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java
new file mode 100644
index 00000000..f7cdd51e
--- /dev/null
+++ b/src/com/android/providers/downloads/DownloadProvider.java
@@ -0,0 +1,731 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.downloads;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.UriMatcher;
+import android.content.pm.PackageManager;
+import android.database.CrossProcessCursor;
+import android.database.Cursor;
+import android.database.CursorWindow;
+import android.database.CursorWrapper;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.database.SQLException;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.provider.Downloads;
+import android.util.Config;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.HashSet;
+
+
+/**
+ * Allows application to interact with the download manager.
+ */
+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;
+ /** Name of table in the database */
+ private static final String DB_TABLE = "downloads";
+
+ /** MIME type for the entire download list */
+ private static final String DOWNLOAD_LIST_TYPE = "vnd.android.cursor.dir/download";
+ /** MIME type for an individual download */
+ private static final String DOWNLOAD_TYPE = "vnd.android.cursor.item/download";
+
+ /** URI matcher used to recognize URIs sent by applications */
+ private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ /** URI matcher constant for the URI of the entire download list */
+ private static final int DOWNLOADS = 1;
+ /** URI matcher constant for the URI of an individual download */
+ private static final int DOWNLOADS_ID = 2;
+ static {
+ sURIMatcher.addURI("downloads", "download", DOWNLOADS);
+ sURIMatcher.addURI("downloads", "download/#", DOWNLOADS_ID);
+ }
+
+ private static final String[] sAppReadableColumnsArray = new String[] {
+ Downloads._ID,
+ Downloads.APP_DATA,
+ Downloads._DATA,
+ Downloads.MIMETYPE,
+ Downloads.VISIBILITY,
+ Downloads.CONTROL,
+ Downloads.STATUS,
+ Downloads.LAST_MODIFICATION,
+ Downloads.NOTIFICATION_PACKAGE,
+ Downloads.NOTIFICATION_CLASS,
+ Downloads.TOTAL_BYTES,
+ Downloads.CURRENT_BYTES,
+ Downloads.TITLE,
+ Downloads.DESCRIPTION
+ };
+
+ private static HashSet<String> sAppReadableColumnsSet;
+ static {
+ sAppReadableColumnsSet = new HashSet<String>();
+ for (int i = 0; i < sAppReadableColumnsArray.length; ++i) {
+ sAppReadableColumnsSet.add(sAppReadableColumnsArray[i]);
+ }
+ }
+
+ /** The database that lies underneath this content provider */
+ private SQLiteOpenHelper mOpenHelper = null;
+
+ /**
+ * Creates and updated database on demand when opening it.
+ * Helper class to create database the first time the provider is
+ * initialized and upgrade it when a new version of the provider needs
+ * an updated version of the database.
+ */
+ private final class DatabaseHelper extends SQLiteOpenHelper {
+
+ public DatabaseHelper(final Context context) {
+ super(context, DB_NAME, null, DB_VERSION);
+ }
+
+ /**
+ * Creates database the first time we try to open it.
+ */
+ @Override
+ public void onCreate(final SQLiteDatabase db) {
+ if (Constants.LOGVV) {
+ Log.v(Constants.TAG, "populating new database");
+ }
+ createTable(db);
+ }
+
+ /* (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: 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;
+ }
+ Log.i(Constants.TAG, "Upgrading downloads database from version " + oldV + " to " + newV
+ + ", which will destroy all old data");
+ dropTable(db);
+ createTable(db);
+ }
+ }
+
+ /**
+ * Initializes the content provider when it is created.
+ */
+ @Override
+ public boolean onCreate() {
+ mOpenHelper = new DatabaseHelper(getContext());
+ return true;
+ }
+
+ /**
+ * Returns the content-provider-style MIME types of the various
+ * types accessible through this content provider.
+ */
+ @Override
+ public String getType(final Uri uri) {
+ int match = sURIMatcher.match(uri);
+ switch (match) {
+ case DOWNLOADS: {
+ return DOWNLOAD_LIST_TYPE;
+ }
+ case DOWNLOADS_ID: {
+ return DOWNLOAD_TYPE;
+ }
+ default: {
+ if (Constants.LOGV) {
+ Log.v(Constants.TAG, "calling getType on an unknown URI: " + uri);
+ }
+ throw new IllegalArgumentException("Unknown URI: " + uri);
+ }
+ }
+ }
+
+ /**
+ * Creates the table that'll hold the download information.
+ */
+ private void createTable(SQLiteDatabase db) {
+ try {
+ db.execSQL("CREATE TABLE " + DB_TABLE + "(" +
+ Downloads._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ Downloads.URI + " TEXT, " +
+ Constants.RETRY_AFTER___REDIRECT_COUNT + " INTEGER, " +
+ Downloads.APP_DATA + " TEXT, " +
+ Downloads.NO_INTEGRITY + " BOOLEAN, " +
+ Downloads.FILENAME_HINT + " TEXT, " +
+ Constants.OTA_UPDATE + " BOOLEAN, " +
+ Downloads._DATA + " TEXT, " +
+ Downloads.MIMETYPE + " TEXT, " +
+ Downloads.DESTINATION + " INTEGER, " +
+ Constants.NO_SYSTEM_FILES + " BOOLEAN, " +
+ Downloads.VISIBILITY + " INTEGER, " +
+ Downloads.CONTROL + " INTEGER, " +
+ Downloads.STATUS + " INTEGER, " +
+ Constants.FAILED_CONNECTIONS + " INTEGER, " +
+ Downloads.LAST_MODIFICATION + " BIGINT, " +
+ Downloads.NOTIFICATION_PACKAGE + " TEXT, " +
+ Downloads.NOTIFICATION_CLASS + " TEXT, " +
+ Downloads.NOTIFICATION_EXTRAS + " TEXT, " +
+ Downloads.COOKIE_DATA + " TEXT, " +
+ Downloads.USER_AGENT + " TEXT, " +
+ Downloads.REFERER + " TEXT, " +
+ Downloads.TOTAL_BYTES + " INTEGER, " +
+ Downloads.CURRENT_BYTES + " INTEGER, " +
+ Constants.ETAG + " TEXT, " +
+ Constants.UID + " INTEGER, " +
+ Downloads.OTHER_UID + " INTEGER, " +
+ Downloads.TITLE + " TEXT, " +
+ Downloads.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
+ */
+ @Override
+ public Uri insert(final Uri uri, final ContentValues values) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+
+ if (sURIMatcher.match(uri) != DOWNLOADS) {
+ if (Config.LOGD) {
+ Log.d(Constants.TAG, "calling insert on an unknown/invalid URI: " + uri);
+ }
+ throw new IllegalArgumentException("Unknown/Invalid URI " + uri);
+ }
+
+ ContentValues filteredValues = new ContentValues();
+
+ copyString(Downloads.URI, values, filteredValues);
+ copyString(Downloads.APP_DATA, values, filteredValues);
+ copyBoolean(Downloads.NO_INTEGRITY, values, filteredValues);
+ copyString(Downloads.FILENAME_HINT, values, filteredValues);
+ copyString(Downloads.MIMETYPE, values, filteredValues);
+ Integer dest = values.getAsInteger(Downloads.DESTINATION);
+ if (dest != null) {
+ if (getContext().checkCallingPermission(Downloads.PERMISSION_ACCESS_ADVANCED)
+ != PackageManager.PERMISSION_GRANTED
+ && dest != Downloads.DESTINATION_EXTERNAL
+ && dest != Downloads.DESTINATION_CACHE_PARTITION_PURGEABLE) {
+ throw new SecurityException("unauthorized destination code");
+ }
+ filteredValues.put(Downloads.DESTINATION, dest);
+ }
+ Integer vis = values.getAsInteger(Downloads.VISIBILITY);
+ if (vis == null) {
+ if (dest == Downloads.DESTINATION_EXTERNAL) {
+ filteredValues.put(Downloads.VISIBILITY,
+ Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+ } else {
+ filteredValues.put(Downloads.VISIBILITY, Downloads.VISIBILITY_HIDDEN);
+ }
+ } else {
+ filteredValues.put(Downloads.VISIBILITY, vis);
+ }
+ copyInteger(Downloads.CONTROL, values, filteredValues);
+ filteredValues.put(Downloads.STATUS, Downloads.STATUS_PENDING);
+ filteredValues.put(Downloads.LAST_MODIFICATION, System.currentTimeMillis());
+ String pckg = values.getAsString(Downloads.NOTIFICATION_PACKAGE);
+ String clazz = values.getAsString(Downloads.NOTIFICATION_CLASS);
+ if (pckg != null && clazz != null) {
+ int uid = Binder.getCallingUid();
+ try {
+ if (uid == 0 ||
+ getContext().getPackageManager().getApplicationInfo(pckg, 0).uid == uid) {
+ filteredValues.put(Downloads.NOTIFICATION_PACKAGE, pckg);
+ filteredValues.put(Downloads.NOTIFICATION_CLASS, clazz);
+ }
+ } catch (PackageManager.NameNotFoundException ex) {
+ /* ignored for now */
+ }
+ }
+ copyString(Downloads.NOTIFICATION_EXTRAS, values, filteredValues);
+ copyString(Downloads.COOKIE_DATA, values, filteredValues);
+ copyString(Downloads.USER_AGENT, values, filteredValues);
+ copyString(Downloads.REFERER, values, filteredValues);
+ if (getContext().checkCallingPermission(Downloads.PERMISSION_ACCESS_ADVANCED)
+ == PackageManager.PERMISSION_GRANTED) {
+ copyInteger(Downloads.OTHER_UID, values, filteredValues);
+ }
+ filteredValues.put(Constants.UID, Binder.getCallingUid());
+ if (Binder.getCallingUid() == 0) {
+ copyInteger(Constants.UID, values, filteredValues);
+ }
+ copyString(Downloads.TITLE, values, filteredValues);
+ copyString(Downloads.DESCRIPTION, values, filteredValues);
+
+ if (Constants.LOGVV) {
+ Log.v(Constants.TAG, "initiating download with UID "
+ + filteredValues.getAsInteger(Constants.UID));
+ if (filteredValues.containsKey(Downloads.OTHER_UID)) {
+ Log.v(Constants.TAG, "other UID " +
+ filteredValues.getAsInteger(Downloads.OTHER_UID));
+ }
+ }
+
+ Context context = getContext();
+ context.startService(new Intent(context, DownloadService.class));
+
+ long rowID = db.insert(DB_TABLE, null, filteredValues);
+
+ Uri ret = null;
+
+ if (rowID != -1) {
+ context.startService(new Intent(context, DownloadService.class));
+ ret = Uri.parse(Downloads.CONTENT_URI + "/" + rowID);
+ context.getContentResolver().notifyChange(uri, null);
+ } else {
+ if (Config.LOGD) {
+ Log.d(Constants.TAG, "couldn't insert into downloads database");
+ }
+ }
+
+ return ret;
+ }
+
+ /**
+ * Starts a database query
+ */
+ @Override
+ public Cursor query(final Uri uri, String[] projection,
+ final String selection, final String[] selectionArgs,
+ final String sort) {
+
+ Helpers.validateSelection(selection, sAppReadableColumnsSet);
+
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+
+ int match = sURIMatcher.match(uri);
+ boolean emptyWhere = true;
+ switch (match) {
+ case DOWNLOADS: {
+ qb.setTables(DB_TABLE);
+ break;
+ }
+ case DOWNLOADS_ID: {
+ qb.setTables(DB_TABLE);
+ qb.appendWhere(Downloads._ID + "=");
+ qb.appendWhere(uri.getPathSegments().get(1));
+ emptyWhere = false;
+ break;
+ }
+ default: {
+ if (Constants.LOGV) {
+ Log.v(Constants.TAG, "querying unknown URI: " + uri);
+ }
+ throw new IllegalArgumentException("Unknown URI: " + uri);
+ }
+ }
+
+ if (Binder.getCallingPid() != Process.myPid() && Binder.getCallingUid() != 0) {
+ if (!emptyWhere) {
+ qb.appendWhere(" AND ");
+ }
+ qb.appendWhere("( " + Constants.UID + "=" + Binder.getCallingUid() + " OR "
+ + Downloads.OTHER_UID + "=" + Binder.getCallingUid() + " )");
+ emptyWhere = false;
+
+ if (projection == null) {
+ projection = sAppReadableColumnsArray;
+ } else {
+ for (int i = 0; i < projection.length; ++i) {
+ if (!sAppReadableColumnsSet.contains(projection[i])) {
+ throw new IllegalArgumentException(
+ "column " + projection[i] + " is not allowed in queries");
+ }
+ }
+ }
+ }
+
+ if (Constants.LOGVV) {
+ java.lang.StringBuilder sb = new java.lang.StringBuilder();
+ sb.append("starting query, database is ");
+ if (db != null) {
+ sb.append("not ");
+ }
+ sb.append("null; ");
+ if (projection == null) {
+ sb.append("projection is null; ");
+ } else if (projection.length == 0) {
+ sb.append("projection is empty; ");
+ } else {
+ for (int i = 0; i < projection.length; ++i) {
+ sb.append("projection[");
+ sb.append(i);
+ sb.append("] is ");
+ sb.append(projection[i]);
+ sb.append("; ");
+ }
+ }
+ sb.append("selection is ");
+ sb.append(selection);
+ sb.append("; ");
+ if (selectionArgs == null) {
+ sb.append("selectionArgs is null; ");
+ } else if (selectionArgs.length == 0) {
+ sb.append("selectionArgs is empty; ");
+ } else {
+ for (int i = 0; i < selectionArgs.length; ++i) {
+ sb.append("selectionArgs[");
+ sb.append(i);
+ sb.append("] is ");
+ sb.append(selectionArgs[i]);
+ sb.append("; ");
+ }
+ }
+ sb.append("sort is ");
+ sb.append(sort);
+ sb.append(".");
+ Log.v(Constants.TAG, sb.toString());
+ }
+
+ Cursor ret = qb.query(db, projection, selection, selectionArgs,
+ null, null, sort);
+
+ if (ret != null) {
+ ret = new ReadOnlyCursorWrapper(ret);
+ }
+
+ if (ret != null) {
+ ret.setNotificationUri(getContext().getContentResolver(), uri);
+ if (Constants.LOGVV) {
+ Log.v(Constants.TAG,
+ "created cursor " + ret + " on behalf of " + Binder.getCallingPid());
+ }
+ } else {
+ if (Constants.LOGV) {
+ Log.v(Constants.TAG, "query failed in downloads database");
+ }
+ }
+
+ return ret;
+ }
+
+ /**
+ * Updates a row in the database
+ */
+ @Override
+ public int update(final Uri uri, final ContentValues values,
+ final String where, final String[] whereArgs) {
+
+ Helpers.validateSelection(where, sAppReadableColumnsSet);
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+
+ int count;
+ long rowId = 0;
+ boolean startService = false;
+
+ ContentValues filteredValues;
+ if (Binder.getCallingPid() != Process.myPid()) {
+ filteredValues = new ContentValues();
+ copyString(Downloads.APP_DATA, values, filteredValues);
+ copyInteger(Downloads.VISIBILITY, values, filteredValues);
+ Integer i = values.getAsInteger(Downloads.CONTROL);
+ if (i != null) {
+ filteredValues.put(Downloads.CONTROL, i);
+ startService = true;
+ }
+ copyInteger(Downloads.CONTROL, values, filteredValues);
+ copyString(Downloads.TITLE, values, filteredValues);
+ copyString(Downloads.DESCRIPTION, values, filteredValues);
+ } else {
+ filteredValues = values;
+ }
+ int match = sURIMatcher.match(uri);
+ switch (match) {
+ case DOWNLOADS:
+ case DOWNLOADS_ID: {
+ String myWhere;
+ if (where != null) {
+ if (match == DOWNLOADS) {
+ myWhere = "( " + where + " )";
+ } else {
+ myWhere = "( " + where + " ) AND ";
+ }
+ } else {
+ myWhere = "";
+ }
+ if (match == DOWNLOADS_ID) {
+ String segment = uri.getPathSegments().get(1);
+ rowId = Long.parseLong(segment);
+ myWhere += " ( " + Downloads._ID + " = " + rowId + " ) ";
+ }
+ if (Binder.getCallingPid() != Process.myPid() && Binder.getCallingUid() != 0) {
+ myWhere += " AND ( " + Constants.UID + "=" + Binder.getCallingUid() + " OR "
+ + Downloads.OTHER_UID + "=" + Binder.getCallingUid() + " )";
+ }
+ if (filteredValues.size() > 0) {
+ count = db.update(DB_TABLE, filteredValues, myWhere, whereArgs);
+ } else {
+ count = 0;
+ }
+ break;
+ }
+ default: {
+ if (Config.LOGD) {
+ Log.d(Constants.TAG, "updating unknown/invalid URI: " + uri);
+ }
+ throw new UnsupportedOperationException("Cannot update URI: " + uri);
+ }
+ }
+ getContext().getContentResolver().notifyChange(uri, null);
+ if (startService) {
+ Context context = getContext();
+ context.startService(new Intent(context, DownloadService.class));
+ }
+ return count;
+ }
+
+ /**
+ * Deletes a row in the database
+ */
+ @Override
+ public int delete(final Uri uri, final String where,
+ final String[] whereArgs) {
+
+ Helpers.validateSelection(where, sAppReadableColumnsSet);
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ int count;
+ int match = sURIMatcher.match(uri);
+ switch (match) {
+ case DOWNLOADS:
+ case DOWNLOADS_ID: {
+ String myWhere;
+ if (where != null) {
+ if (match == DOWNLOADS) {
+ myWhere = "( " + where + " )";
+ } else {
+ myWhere = "( " + where + " ) AND ";
+ }
+ } else {
+ myWhere = "";
+ }
+ if (match == DOWNLOADS_ID) {
+ String segment = uri.getPathSegments().get(1);
+ long rowId = Long.parseLong(segment);
+ myWhere += " ( " + Downloads._ID + " = " + rowId + " ) ";
+ }
+ if (Binder.getCallingPid() != Process.myPid() && Binder.getCallingUid() != 0) {
+ myWhere += " AND ( " + Constants.UID + "=" + Binder.getCallingUid() + " OR "
+ + Downloads.OTHER_UID + "=" + Binder.getCallingUid() + " )";
+ }
+ count = db.delete(DB_TABLE, myWhere, whereArgs);
+ break;
+ }
+ default: {
+ if (Config.LOGD) {
+ Log.d(Constants.TAG, "deleting unknown/invalid URI: " + uri);
+ }
+ throw new UnsupportedOperationException("Cannot delete URI: " + uri);
+ }
+ }
+ getContext().getContentResolver().notifyChange(uri, null);
+ return count;
+ }
+
+ /**
+ * Remotely opens a file
+ */
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode)
+ throws FileNotFoundException {
+ if (Constants.LOGVV) {
+ Log.v(Constants.TAG, "openFile uri: " + uri + ", mode: " + mode
+ + ", uid: " + Binder.getCallingUid());
+ Cursor cursor = query(Downloads.CONTENT_URI, new String[] { "_id" }, null, null, "_id");
+ if (cursor == null) {
+ Log.v(Constants.TAG, "null cursor in openFile");
+ } else {
+ if (!cursor.moveToFirst()) {
+ Log.v(Constants.TAG, "empty cursor in openFile");
+ } else {
+ do {
+ Log.v(Constants.TAG, "row " + cursor.getInt(0) + " available");
+ } while(cursor.moveToNext());
+ }
+ cursor.close();
+ }
+ cursor = query(uri, new String[] { "_data" }, null, null, null);
+ if (cursor == null) {
+ Log.v(Constants.TAG, "null cursor in openFile");
+ } else {
+ if (!cursor.moveToFirst()) {
+ Log.v(Constants.TAG, "empty cursor in openFile");
+ } else {
+ String filename = cursor.getString(0);
+ Log.v(Constants.TAG, "filename in openFile: " + filename);
+ if (new java.io.File(filename).isFile()) {
+ Log.v(Constants.TAG, "file exists in openFile");
+ }
+ }
+ cursor.close();
+ }
+ }
+
+ // This logic is mostly copied form openFileHelper. If openFileHelper eventually
+ // gets split into small bits (to extract the filename and the modebits),
+ // this code could use the separate bits and be deeply simplified.
+ Cursor c = query(uri, new String[]{"_data"}, null, null, null);
+ int count = (c != null) ? c.getCount() : 0;
+ if (count != 1) {
+ // If there is not exactly one result, throw an appropriate exception.
+ if (c != null) {
+ c.close();
+ }
+ if (count == 0) {
+ throw new FileNotFoundException("No entry for " + uri);
+ }
+ throw new FileNotFoundException("Multiple items at " + uri);
+ }
+
+ c.moveToFirst();
+ String path = c.getString(0);
+ c.close();
+ if (path == null) {
+ throw new FileNotFoundException("No filename found.");
+ }
+ if (!Helpers.isFilenameValid(path)) {
+ throw new FileNotFoundException("Invalid filename.");
+ }
+
+ if (!"r".equals(mode)) {
+ throw new FileNotFoundException("Bad mode for " + uri + ": " + mode);
+ }
+ ParcelFileDescriptor ret = ParcelFileDescriptor.open(new File(path),
+ ParcelFileDescriptor.MODE_READ_ONLY);
+
+ if (ret == null) {
+ if (Constants.LOGV) {
+ Log.v(Constants.TAG, "couldn't open file");
+ }
+ throw new FileNotFoundException("couldn't open file");
+ } else {
+ ContentValues values = new ContentValues();
+ values.put(Downloads.LAST_MODIFICATION, System.currentTimeMillis());
+ update(uri, values, null, null);
+ }
+ return ret;
+ }
+
+ private static final void copyInteger(String key, ContentValues from, ContentValues to) {
+ Integer i = from.getAsInteger(key);
+ if (i != null) {
+ to.put(key, i);
+ }
+ }
+
+ private static final void copyBoolean(String key, ContentValues from, ContentValues to) {
+ Boolean b = from.getAsBoolean(key);
+ if (b != null) {
+ to.put(key, b);
+ }
+ }
+
+ private static final void copyString(String key, ContentValues from, ContentValues to) {
+ String s = from.getAsString(key);
+ if (s != null) {
+ to.put(key, s);
+ }
+ }
+
+ private class ReadOnlyCursorWrapper extends CursorWrapper implements CrossProcessCursor {
+ public ReadOnlyCursorWrapper(Cursor cursor) {
+ super(cursor);
+ mCursor = (CrossProcessCursor) cursor;
+ }
+
+ public boolean deleteRow() {
+ throw new SecurityException("Download manager cursors are read-only");
+ }
+
+ public boolean commitUpdates() {
+ throw new SecurityException("Download manager cursors are read-only");
+ }
+
+ public void fillWindow(int pos, CursorWindow window) {
+ mCursor.fillWindow(pos, window);
+ }
+
+ public CursorWindow getWindow() {
+ return mCursor.getWindow();
+ }
+
+ public boolean onMove(int oldPosition, int newPosition) {
+ return mCursor.onMove(oldPosition, newPosition);
+ }
+
+ private CrossProcessCursor mCursor;
+ }
+
+}