2 * Copyright (C) 2007 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.providers.downloads;
19 import android.content.ContentProvider;
20 import android.content.ContentValues;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.UriMatcher;
24 import android.content.pm.PackageManager;
25 import android.database.Cursor;
26 import android.database.sqlite.SQLiteDatabase;
27 import android.database.sqlite.SQLiteOpenHelper;
28 import android.database.sqlite.SQLiteQueryBuilder;
29 import android.database.SQLException;
30 import android.net.Uri;
31 import android.os.Binder;
32 import android.os.ParcelFileDescriptor;
33 import android.os.Process;
34 import android.provider.BaseColumns;
35 import android.provider.Downloads;
36 import android.util.Config;
37 import android.util.Log;
39 import java.io.FileNotFoundException;
42 * Allows application to interact with the download manager.
44 public final class DownloadProvider extends ContentProvider {
46 /** Tag used in logging */
47 private static final String TAG = Constants.TAG;
49 /** Database filename */
50 private static final String DB_NAME = "downloads.db";
51 /** Current database vesion */
52 private static final int DB_VERSION = 31;
53 /** Name of table in the database */
54 private static final String DB_TABLE = "downloads";
56 /** MIME type for the entire download list */
57 private static final String DOWNLOAD_LIST_TYPE = "vnd.android.cursor.dir/download";
58 /** MIME type for an individual download */
59 private static final String DOWNLOAD_TYPE = "vnd.android.cursor.item/download";
61 /** URI matcher used to recognize URIs sent by applications */
62 private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
63 /** URI matcher constant for the URI of the entire download list */
64 private static final int DOWNLOADS = 1;
65 /** URI matcher constant for the URI of an individual download */
66 private static final int DOWNLOADS_ID = 2;
68 sURIMatcher.addURI("downloads", "download", DOWNLOADS);
69 sURIMatcher.addURI("downloads", "download/#", DOWNLOADS_ID);
72 /** The database that lies underneath this content provider */
73 private SQLiteOpenHelper mOpenHelper = null;
76 * Creates and updated database on demand when opening it.
77 * Helper class to create database the first time the provider is
78 * initialized and upgrade it when a new version of the provider needs
79 * an updated version of the database.
81 private final class DatabaseHelper extends SQLiteOpenHelper {
83 public DatabaseHelper(final Context context) {
84 super(context, DB_NAME, null, DB_VERSION);
88 * Creates database the first time we try to open it.
91 public void onCreate(final SQLiteDatabase db) {
92 if (Constants.LOGVV) {
93 Log.v(Constants.TAG, "populating new database");
98 /* (not a javadoc comment)
99 * Checks data integrity when opening the database.
103 * public void onOpen(final SQLiteDatabase db) {
109 * Updates the database format when a content provider is used
110 * with a database that was created with a different format.
112 // Note: technically, this could also be a downgrade, so if we want
113 // to gracefully handle upgrades we should be careful about
114 // what to do on downgrades.
116 public void onUpgrade(final SQLiteDatabase db, final int oldV, final int newV) {
117 Log.i(TAG, "Upgrading downloads database from version " + oldV + " to " + newV
118 + ", which will destroy all old data");
125 * Initializes the content provider when it is created.
128 public boolean onCreate() {
129 mOpenHelper = new DatabaseHelper(getContext());
134 * Returns the content-provider-style MIME types of the various
135 * types accessible through this content provider.
138 public String getType(final Uri uri) {
139 int match = sURIMatcher.match(uri);
142 return DOWNLOAD_LIST_TYPE;
145 return DOWNLOAD_TYPE;
148 if (Constants.LOGV) {
149 Log.v(Constants.TAG, "calling getType on an unknown URI: " + uri);
151 throw new IllegalArgumentException("Unknown URI: " + uri);
157 * Creates the table that'll hold the download information.
159 private void createTable(SQLiteDatabase db) {
161 db.execSQL("CREATE TABLE " + DB_TABLE + "(" +
162 BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
163 Downloads.URI + " TEXT, " +
164 Downloads.METHOD + " INTEGER, " +
165 Downloads.ENTITY + " TEXT, " +
166 Downloads.NO_INTEGRITY + " BOOLEAN, " +
167 Downloads.FILENAME_HINT + " TEXT, " +
168 Downloads.OTA_UPDATE + " BOOLEAN, " +
169 Downloads.FILENAME + " TEXT, " +
170 Downloads.MIMETYPE + " TEXT, " +
171 Downloads.DESTINATION + " INTEGER, " +
172 Downloads.NO_SYSTEM_FILES + " BOOLEAN, " +
173 Downloads.VISIBILITY + " INTEGER, " +
174 Downloads.CONTROL + " INTEGER, " +
175 Downloads.STATUS + " INTEGER, " +
176 Downloads.FAILED_CONNECTIONS + " INTEGER, " +
177 Downloads.LAST_MODIFICATION + " BIGINT, " +
178 Downloads.NOTIFICATION_PACKAGE + " TEXT, " +
179 Downloads.NOTIFICATION_CLASS + " TEXT, " +
180 Downloads.NOTIFICATION_EXTRAS + " TEXT, " +
181 Downloads.COOKIE_DATA + " TEXT, " +
182 Downloads.USER_AGENT + " TEXT, " +
183 Downloads.REFERER + " TEXT, " +
184 Downloads.TOTAL_BYTES + " INTEGER, " +
185 Downloads.CURRENT_BYTES + " INTEGER, " +
186 Downloads.ETAG + " TEXT, " +
187 Downloads.UID + " INTEGER, " +
188 Downloads.OTHER_UID + " INTEGER, " +
189 Downloads.TITLE + " TEXT, " +
190 Downloads.DESCRIPTION + " TEXT, " +
191 Downloads.MEDIA_SCANNED + " BOOLEAN);");
192 } catch (SQLException ex) {
193 Log.e(Constants.TAG, "couldn't create table in downloads database");
199 * Deletes the table that holds the download information.
201 private void dropTable(SQLiteDatabase db) {
203 db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE);
204 } catch (SQLException ex) {
205 Log.e(Constants.TAG, "couldn't drop table in downloads database");
211 * Inserts a row in the database
214 public Uri insert(final Uri uri, final ContentValues values) {
215 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
217 if (sURIMatcher.match(uri) != DOWNLOADS) {
219 Log.d(Constants.TAG, "calling insert on an unknown/invalid URI: " + uri);
221 throw new IllegalArgumentException("Unknown/Invalid URI " + uri);
224 boolean hasUID = values.containsKey(Downloads.UID);
225 if (hasUID && Binder.getCallingUid() != 0) {
226 values.remove(Downloads.UID);
230 values.put(Downloads.UID, Binder.getCallingUid());
232 if (Constants.LOGVV) {
233 Log.v(TAG, "initiating download with UID " + Binder.getCallingUid());
234 if (values.containsKey(Downloads.OTHER_UID)) {
235 Log.v(TAG, "other UID " + values.getAsInteger(Downloads.OTHER_UID));
239 if (values.containsKey(Downloads.LAST_MODIFICATION)) {
240 values.remove(Downloads.LAST_MODIFICATION);
242 values.put(Downloads.LAST_MODIFICATION, System.currentTimeMillis());
244 if (values.containsKey(Downloads.STATUS)) {
245 values.remove(Downloads.STATUS);
247 values.put(Downloads.STATUS, Downloads.STATUS_PENDING);
249 if (values.containsKey(Downloads.OTA_UPDATE)
250 && getContext().checkCallingPermission(Constants.OTA_UPDATE_PERMISSION)
251 != PackageManager.PERMISSION_GRANTED) {
252 values.remove(Downloads.OTA_UPDATE);
255 Context context = getContext();
256 context.startService(new Intent(context, DownloadService.class));
258 long rowID = db.insert(DB_TABLE, null, values);
263 context.startService(new Intent(context, DownloadService.class));
264 ret = Uri.parse(Downloads.CONTENT_URI + "/" + rowID);
265 context.getContentResolver().notifyChange(uri, null);
268 Log.d(TAG, "couldn't insert into downloads database");
276 * Starts a database query
279 public Cursor query(final Uri uri, final String[] projection,
280 final String selection, final String[] selectionArgs,
282 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
284 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
286 int match = sURIMatcher.match(uri);
287 boolean emptyWhere = true;
290 qb.setTables(DB_TABLE);
294 qb.setTables(DB_TABLE);
295 qb.appendWhere(BaseColumns._ID + "=");
296 qb.appendWhere(uri.getPathSegments().get(1));
301 if (Constants.LOGV) {
302 Log.v(TAG, "querying unknown URI: " + uri);
304 throw new IllegalArgumentException("Unknown URI: " + uri);
308 if (Binder.getCallingPid() != Process.myPid()
309 && Binder.getCallingUid() != 0
310 && getContext().checkCallingPermission(Constants.UI_PERMISSION)
311 != PackageManager.PERMISSION_GRANTED) {
313 qb.appendWhere(" AND ");
315 qb.appendWhere("( " + Downloads.UID + "=" + Binder.getCallingUid() + " OR "
316 + Downloads.OTHER_UID + "=" + Binder.getCallingUid() + " )");
320 if (Constants.LOGVV) {
321 java.lang.StringBuilder sb = new java.lang.StringBuilder();
322 sb.append("starting query, database is ");
327 if (projection == null) {
328 sb.append("projection is null; ");
329 } else if (projection.length == 0) {
330 sb.append("projection is empty; ");
332 for (int i = 0; i < projection.length; ++i) {
333 sb.append("projection[");
336 sb.append(projection[i]);
340 sb.append("selection is ");
341 sb.append(selection);
343 if (selectionArgs == null) {
344 sb.append("selectionArgs is null; ");
345 } else if (selectionArgs.length == 0) {
346 sb.append("selectionArgs is empty; ");
348 for (int i = 0; i < selectionArgs.length; ++i) {
349 sb.append("selectionArgs[");
352 sb.append(selectionArgs[i]);
356 sb.append("sort is ");
359 Log.v(TAG, sb.toString());
362 Cursor ret = qb.query(db, projection, selection, selectionArgs,
366 ret.setNotificationUri(getContext().getContentResolver(), uri);
367 if (Constants.LOGVV) {
369 "created cursor " + ret + " on behalf of " + Binder.getCallingPid());
372 if (Constants.LOGV) {
373 Log.v(TAG, "query failed in downloads database");
381 * Updates a row in the database
384 public int update(final Uri uri, final ContentValues values,
385 final String where, final String[] whereArgs) {
386 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
390 if (values.containsKey(Downloads.UID)) {
391 values.remove(Downloads.UID);
393 int match = sURIMatcher.match(uri);
399 if (match == DOWNLOADS) {
402 myWhere = where + " AND ";
407 if (match == DOWNLOADS_ID) {
408 String segment = uri.getPathSegments().get(1);
409 rowId = Long.parseLong(segment);
410 myWhere += Downloads._ID + " = " + rowId;
412 if (Binder.getCallingPid() != Process.myPid()
413 && Binder.getCallingUid() != 0
414 && getContext().checkCallingPermission(Constants.UI_PERMISSION)
415 != PackageManager.PERMISSION_GRANTED) {
416 myWhere += " AND ( " + Downloads.UID + "=" + Binder.getCallingUid() + " OR "
417 + Downloads.OTHER_UID + "=" + Binder.getCallingUid() + " )";
419 count = db.update(DB_TABLE, values, myWhere, whereArgs);
424 Log.d(TAG, "updating unknown/invalid URI: " + uri);
426 throw new UnsupportedOperationException("Cannot update URI: " + uri);
429 getContext().getContentResolver().notifyChange(uri, null);
434 * Deletes a row in the database
437 public int delete(final Uri uri, final String where,
438 final String[] whereArgs) {
439 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
441 int match = sURIMatcher.match(uri);
447 if (match == DOWNLOADS) {
450 myWhere = where + " AND ";
455 if (match == DOWNLOADS_ID) {
456 String segment = uri.getPathSegments().get(1);
457 long rowId = Long.parseLong(segment);
458 myWhere += Downloads._ID + " = " + rowId;
460 if (Binder.getCallingPid() != Process.myPid()
461 && Binder.getCallingUid() != 0
462 && getContext().checkCallingPermission(Constants.UI_PERMISSION)
463 != PackageManager.PERMISSION_GRANTED) {
464 myWhere += " AND ( " + Downloads.UID + "=" + Binder.getCallingUid() + " OR "
465 + Downloads.OTHER_UID + "=" + Binder.getCallingUid() + " )";
467 count = db.delete(DB_TABLE, myWhere, whereArgs);
472 Log.d(TAG, "deleting unknown/invalid URI: " + uri);
474 throw new UnsupportedOperationException("Cannot delete URI: " + uri);
477 getContext().getContentResolver().notifyChange(uri, null);
482 * Remotely opens a file
485 public ParcelFileDescriptor openFile(Uri uri, String mode)
486 throws FileNotFoundException {
487 if (Constants.LOGVV) {
488 Log.v(TAG, "openFile uri: " + uri + ", mode: " + mode
489 + ", uid: " + Binder.getCallingUid());
490 Cursor cursor = query(Downloads.CONTENT_URI, new String[] { "_id" }, null, null, "_id");
491 if (cursor == null) {
492 Log.v(TAG, "null cursor in openFile");
494 if (!cursor.moveToFirst()) {
495 Log.v(TAG, "empty cursor in openFile");
498 Log.v(TAG, "row " + cursor.getInt(0) + " available");
499 } while(cursor.moveToNext());
503 cursor = query(uri, new String[] { "_data" }, null, null, null);
504 if (cursor == null) {
505 Log.v(TAG, "null cursor in openFile");
507 if (!cursor.moveToFirst()) {
508 Log.v(TAG, "empty cursor in openFile");
510 String filename = cursor.getString(0);
511 Log.v(TAG, "filename in openFile: " + filename);
512 if (new java.io.File(filename).isFile()) {
513 Log.v(TAG, "file exists in openFile");
519 ParcelFileDescriptor ret = openFileHelper(uri, mode);
522 Log.d(TAG, "couldn't open file");
525 ContentValues values = new ContentValues();
526 values.put(Downloads.LAST_MODIFICATION, System.currentTimeMillis());
527 update(uri, values, null, null);