blob: d7c24f9a7b7f230365446d155ace8c1a75f4c5ea [file] [log] [blame]
/*
* 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.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.CrossProcessCursor;
import android.database.Cursor;
import android.database.CursorWindow;
import android.database.CursorWrapper;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
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.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.Impl._ID,
Downloads.Impl.COLUMN_APP_DATA,
Downloads.Impl._DATA,
Downloads.Impl.COLUMN_MIME_TYPE,
Downloads.Impl.COLUMN_VISIBILITY,
Downloads.Impl.COLUMN_DESTINATION,
Downloads.Impl.COLUMN_CONTROL,
Downloads.Impl.COLUMN_STATUS,
Downloads.Impl.COLUMN_LAST_MODIFICATION,
Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE,
Downloads.Impl.COLUMN_NOTIFICATION_CLASS,
Downloads.Impl.COLUMN_TOTAL_BYTES,
Downloads.Impl.COLUMN_CURRENT_BYTES,
Downloads.Impl.COLUMN_TITLE,
Downloads.Impl.COLUMN_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;
/** List of uids that can access the downloads */
private int mSystemUid = -1;
private int mDefContainerUid = -1;
/**
* 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());
// Initialize the system uid
mSystemUid = Process.SYSTEM_UID;
// Initialize the default container uid. Package name hardcoded
// for now.
ApplicationInfo appInfo = null;
try {
appInfo = getContext().getPackageManager().
getApplicationInfo("com.android.defcontainer", 0);
} catch (NameNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (appInfo != null) {
mDefContainerUid = appInfo.uid;
}
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.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
*/
@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.Impl.COLUMN_URI, values, filteredValues);
copyString(Downloads.Impl.COLUMN_APP_DATA, values, filteredValues);
copyBoolean(Downloads.Impl.COLUMN_NO_INTEGRITY, values, filteredValues);
copyString(Downloads.Impl.COLUMN_FILE_NAME_HINT, values, filteredValues);
copyString(Downloads.Impl.COLUMN_MIME_TYPE, values, filteredValues);
Integer dest = values.getAsInteger(Downloads.Impl.COLUMN_DESTINATION);
if (dest != null) {
if (getContext().checkCallingPermission(Downloads.Impl.PERMISSION_ACCESS_ADVANCED)
!= PackageManager.PERMISSION_GRANTED
&& dest != Downloads.Impl.DESTINATION_EXTERNAL
&& dest != Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE) {
throw new SecurityException("unauthorized destination code");
}
filteredValues.put(Downloads.Impl.COLUMN_DESTINATION, dest);
}
Integer vis = values.getAsInteger(Downloads.Impl.COLUMN_VISIBILITY);
if (vis == null) {
if (dest == Downloads.Impl.DESTINATION_EXTERNAL) {
filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY,
Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
} else {
filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY,
Downloads.Impl.VISIBILITY_HIDDEN);
}
} else {
filteredValues.put(Downloads.Impl.COLUMN_VISIBILITY, vis);
}
copyInteger(Downloads.Impl.COLUMN_CONTROL, values, filteredValues);
filteredValues.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
filteredValues.put(Downloads.Impl.COLUMN_LAST_MODIFICATION, System.currentTimeMillis());
String pckg = values.getAsString(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE);
String clazz = values.getAsString(Downloads.Impl.COLUMN_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.Impl.COLUMN_NOTIFICATION_PACKAGE, pckg);
filteredValues.put(Downloads.Impl.COLUMN_NOTIFICATION_CLASS, clazz);
}
} catch (PackageManager.NameNotFoundException ex) {
/* ignored for now */
}
}
copyString(Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS, values, filteredValues);
copyString(Downloads.Impl.COLUMN_COOKIE_DATA, values, filteredValues);
copyString(Downloads.Impl.COLUMN_USER_AGENT, values, filteredValues);
copyString(Downloads.Impl.COLUMN_REFERER, values, filteredValues);
if (getContext().checkCallingPermission(Downloads.Impl.PERMISSION_ACCESS_ADVANCED)
== PackageManager.PERMISSION_GRANTED) {
copyInteger(Downloads.Impl.COLUMN_OTHER_UID, values, filteredValues);
}
filteredValues.put(Constants.UID, Binder.getCallingUid());
if (Binder.getCallingUid() == 0) {
copyInteger(Constants.UID, values, filteredValues);
}
copyString(Downloads.Impl.COLUMN_TITLE, values, filteredValues);
copyString(Downloads.Impl.COLUMN_DESCRIPTION, values, filteredValues);
if (Constants.LOGVV) {
Log.v(Constants.TAG, "initiating download with UID "
+ filteredValues.getAsInteger(Constants.UID));
if (filteredValues.containsKey(Downloads.Impl.COLUMN_OTHER_UID)) {
Log.v(Constants.TAG, "other UID " +
filteredValues.getAsInteger(Downloads.Impl.COLUMN_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.Impl.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.Impl._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);
}
}
int callingUid = Binder.getCallingUid();
if (Binder.getCallingPid() != Process.myPid() &&
callingUid != mSystemUid &&
callingUid != mDefContainerUid &&
Process.supportsProcesses()) {
boolean canSeeAllExternal;
if (projection == null) {
projection = sAppReadableColumnsArray;
// sAppReadableColumnsArray includes _DATA, which is not allowed
// to be seen except by the initiating application
canSeeAllExternal = false;
} else {
canSeeAllExternal = getContext().checkCallingPermission(
Downloads.Impl.PERMISSION_SEE_ALL_EXTERNAL)
== PackageManager.PERMISSION_GRANTED;
for (int i = 0; i < projection.length; ++i) {
if (!sAppReadableColumnsSet.contains(projection[i])) {
throw new IllegalArgumentException(
"column " + projection[i] + " is not allowed in queries");
}
canSeeAllExternal = canSeeAllExternal
&& !projection[i].equals(Downloads.Impl._DATA);
}
}
if (!emptyWhere) {
qb.appendWhere(" AND ");
emptyWhere = false;
}
String validUid = "( " + Constants.UID + "="
+ Binder.getCallingUid() + " OR "
+ Downloads.Impl.COLUMN_OTHER_UID + "="
+ Binder.getCallingUid() + " )";
if (canSeeAllExternal) {
qb.appendWhere("( " + validUid + " OR "
+ Downloads.Impl.DESTINATION_EXTERNAL + " = "
+ Downloads.Impl.COLUMN_DESTINATION + " )");
} else {
qb.appendWhere(validUid);
}
}
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.Impl.COLUMN_APP_DATA, values, filteredValues);
copyInteger(Downloads.Impl.COLUMN_VISIBILITY, values, filteredValues);
Integer i = values.getAsInteger(Downloads.Impl.COLUMN_CONTROL);
if (i != null) {
filteredValues.put(Downloads.Impl.COLUMN_CONTROL, i);
startService = true;
}
copyInteger(Downloads.Impl.COLUMN_CONTROL, values, filteredValues);
copyString(Downloads.Impl.COLUMN_TITLE, values, filteredValues);
copyString(Downloads.Impl.COLUMN_DESCRIPTION, values, filteredValues);
} else {
filteredValues = values;
String filename = values.getAsString(Downloads.Impl._DATA);
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());
}
c.close();
}
}
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.Impl._ID + " = " + rowId + " ) ";
}
int callingUid = Binder.getCallingUid();
if (Binder.getCallingPid() != Process.myPid() &&
callingUid != mSystemUid &&
callingUid != mDefContainerUid) {
myWhere += " AND ( " + Constants.UID + "=" + Binder.getCallingUid() + " OR "
+ Downloads.Impl.COLUMN_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.Impl._ID + " = " + rowId + " ) ";
}
int callingUid = Binder.getCallingUid();
if (Binder.getCallingPid() != Process.myPid() &&
callingUid != mSystemUid &&
callingUid != mDefContainerUid) {
myWhere += " AND ( " + Constants.UID + "=" + Binder.getCallingUid() + " OR "
+ Downloads.Impl.COLUMN_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.Impl.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.Impl.COLUMN_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;
}
}