OSDN Git Service

c85c94a92f79927468c1dfa38593b4be06bdbcb2
[android-x86/packages-providers-DownloadProvider.git] / src / com / android / providers / downloads / DownloadProvider.java
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.android.providers.downloads;
18
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;
38
39 import java.io.FileNotFoundException;
40
41 /**
42  * Allows application to interact with the download manager.
43  */
44 public final class DownloadProvider extends ContentProvider {
45
46     /** Tag used in logging */
47     private static final String TAG = Constants.TAG;
48
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";
55
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";
60
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;
67     static {
68         sURIMatcher.addURI("downloads", "download", DOWNLOADS);
69         sURIMatcher.addURI("downloads", "download/#", DOWNLOADS_ID);
70     }
71
72     /** The database that lies underneath this content provider */
73     private SQLiteOpenHelper mOpenHelper = null;
74
75     /**
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.
80      */
81     private final class DatabaseHelper extends SQLiteOpenHelper {
82
83         public DatabaseHelper(final Context context) {
84             super(context, DB_NAME, null, DB_VERSION);
85         }
86
87         /**
88          * Creates database the first time we try to open it.
89          */
90         @Override
91         public void onCreate(final SQLiteDatabase db) {
92             if (Constants.LOGVV) {
93                 Log.v(Constants.TAG, "populating new database");
94             }
95             createTable(db);
96         }
97
98         /* (not a javadoc comment)
99          * Checks data integrity when opening the database.
100          */
101         /*
102          * @Override
103          * public void onOpen(final SQLiteDatabase db) {
104          *     super.onOpen(db);
105          * }
106          */
107
108         /**
109          * Updates the database format when a content provider is used
110          * with a database that was created with a different format.
111          */
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.
115         @Override
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");
119             dropTable(db);
120             createTable(db);
121         }
122     }
123
124     /**
125      * Initializes the content provider when it is created.
126      */
127     @Override
128     public boolean onCreate() {
129         mOpenHelper = new DatabaseHelper(getContext());
130         return true;
131     }
132
133     /**
134      * Returns the content-provider-style MIME types of the various
135      * types accessible through this content provider.
136      */
137     @Override
138     public String getType(final Uri uri) {
139         int match = sURIMatcher.match(uri);
140         switch (match) {
141             case DOWNLOADS: {
142                 return DOWNLOAD_LIST_TYPE;
143             }
144             case DOWNLOADS_ID: {
145                 return DOWNLOAD_TYPE;
146             }
147             default: {
148                 if (Constants.LOGV) {
149                     Log.v(Constants.TAG, "calling getType on an unknown URI: " + uri);
150                 }
151                 throw new IllegalArgumentException("Unknown URI: " + uri);
152             }
153         }
154     }
155
156     /**
157      * Creates the table that'll hold the download information.
158      */
159     private void createTable(SQLiteDatabase db) {
160         try {
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");
194             throw ex;
195         }
196     }
197
198     /**
199      * Deletes the table that holds the download information.
200      */
201     private void dropTable(SQLiteDatabase db) {
202         try {
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");
206             throw ex;
207         }
208     }
209
210     /**
211      * Inserts a row in the database
212      */
213     @Override
214     public Uri insert(final Uri uri, final ContentValues values) {
215         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
216
217         if (sURIMatcher.match(uri) != DOWNLOADS) {
218             if (Config.LOGD) {
219                 Log.d(Constants.TAG, "calling insert on an unknown/invalid URI: " + uri);
220             }
221             throw new IllegalArgumentException("Unknown/Invalid URI " + uri);
222         }
223
224         boolean hasUID = values.containsKey(Downloads.UID);
225         if (hasUID && Binder.getCallingUid() != 0) {
226             values.remove(Downloads.UID);
227             hasUID = false;
228         }
229         if (!hasUID) {
230             values.put(Downloads.UID, Binder.getCallingUid());
231         }
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));
236             }
237         }
238
239         if (values.containsKey(Downloads.LAST_MODIFICATION)) {
240             values.remove(Downloads.LAST_MODIFICATION);
241         }
242         values.put(Downloads.LAST_MODIFICATION, System.currentTimeMillis());
243
244         if (values.containsKey(Downloads.STATUS)) {
245             values.remove(Downloads.STATUS);
246         }
247         values.put(Downloads.STATUS, Downloads.STATUS_PENDING);
248
249         if (values.containsKey(Downloads.OTA_UPDATE)
250                 && getContext().checkCallingPermission(Constants.OTA_UPDATE_PERMISSION)
251                         != PackageManager.PERMISSION_GRANTED) {
252             values.remove(Downloads.OTA_UPDATE);
253         }
254
255         Context context = getContext();
256         context.startService(new Intent(context, DownloadService.class));
257
258         long rowID = db.insert(DB_TABLE, null, values);
259
260         Uri ret = null;
261
262         if (rowID != -1) {
263             context.startService(new Intent(context, DownloadService.class));
264             ret = Uri.parse(Downloads.CONTENT_URI + "/" + rowID);
265             context.getContentResolver().notifyChange(uri, null);
266         } else {
267             if (Config.LOGD) {
268                 Log.d(TAG, "couldn't insert into downloads database");
269             }
270         }
271
272         return ret;
273     }
274
275     /**
276      * Starts a database query
277      */
278     @Override
279     public Cursor query(final Uri uri, final String[] projection,
280              final String selection, final String[] selectionArgs,
281              final String sort) {
282         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
283
284         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
285
286         int match = sURIMatcher.match(uri);
287         boolean emptyWhere = true;
288         switch (match) {
289             case DOWNLOADS: {
290                 qb.setTables(DB_TABLE);
291                 break;
292             }
293             case DOWNLOADS_ID: {
294                 qb.setTables(DB_TABLE);
295                 qb.appendWhere(BaseColumns._ID + "=");
296                 qb.appendWhere(uri.getPathSegments().get(1));
297                 emptyWhere = false;
298                 break;
299             }
300             default: {
301                 if (Constants.LOGV) {
302                     Log.v(TAG, "querying unknown URI: " + uri);
303                 }
304                 throw new IllegalArgumentException("Unknown URI: " + uri);
305             }
306         }
307
308         if (Binder.getCallingPid() != Process.myPid()
309                 && Binder.getCallingUid() != 0
310                 && getContext().checkCallingPermission(Constants.UI_PERMISSION)
311                         != PackageManager.PERMISSION_GRANTED) {
312             if (!emptyWhere) {
313                 qb.appendWhere(" AND ");
314             }
315             qb.appendWhere("( " + Downloads.UID + "=" +  Binder.getCallingUid() + " OR "
316                     + Downloads.OTHER_UID + "=" +  Binder.getCallingUid() + " )");
317             emptyWhere = false;
318         }
319
320         if (Constants.LOGVV) {
321             java.lang.StringBuilder sb = new java.lang.StringBuilder();
322             sb.append("starting query, database is ");
323             if (db != null) {
324                 sb.append("not ");
325             }
326             sb.append("null; ");
327             if (projection == null) {
328                 sb.append("projection is null; ");
329             } else if (projection.length == 0) {
330                 sb.append("projection is empty; ");
331             } else {
332                 for (int i = 0; i < projection.length; ++i) {
333                     sb.append("projection[");
334                     sb.append(i);
335                     sb.append("] is ");
336                     sb.append(projection[i]);
337                     sb.append("; ");
338                 }
339             }
340             sb.append("selection is ");
341             sb.append(selection);
342             sb.append("; ");
343             if (selectionArgs == null) {
344                 sb.append("selectionArgs is null; ");
345             } else if (selectionArgs.length == 0) {
346                 sb.append("selectionArgs is empty; ");
347             } else {
348                 for (int i = 0; i < selectionArgs.length; ++i) {
349                     sb.append("selectionArgs[");
350                     sb.append(i);
351                     sb.append("] is ");
352                     sb.append(selectionArgs[i]);
353                     sb.append("; ");
354                 }
355             }
356             sb.append("sort is ");
357             sb.append(sort);
358             sb.append(".");
359             Log.v(TAG, sb.toString());
360         }
361
362         Cursor ret = qb.query(db, projection, selection, selectionArgs,
363                               null, null, sort);
364
365         if (ret != null) {
366             ret.setNotificationUri(getContext().getContentResolver(), uri);
367             if (Constants.LOGVV) {
368                 Log.v(Constants.TAG,
369                         "created cursor " + ret + " on behalf of " + Binder.getCallingPid());
370             }
371         } else {
372             if (Constants.LOGV) {
373                 Log.v(TAG, "query failed in downloads database");
374             }
375         }
376
377         return ret;
378     }
379
380     /**
381      * Updates a row in the database
382      */
383     @Override
384     public int update(final Uri uri, final ContentValues values,
385             final String where, final String[] whereArgs) {
386         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
387
388         int count;
389         long rowId = 0;
390         if (values.containsKey(Downloads.UID)) {
391             values.remove(Downloads.UID);
392         }
393         int match = sURIMatcher.match(uri);
394         switch (match) {
395             case DOWNLOADS:
396             case DOWNLOADS_ID: {
397                 String myWhere;
398                 if (where != null) {
399                     if (match == DOWNLOADS) {
400                         myWhere = where;
401                     } else {
402                         myWhere = where + " AND ";
403                     }
404                 } else {
405                     myWhere = "";
406                 }
407                 if (match == DOWNLOADS_ID) {
408                     String segment = uri.getPathSegments().get(1);
409                     rowId = Long.parseLong(segment);
410                     myWhere += Downloads._ID + " = " + rowId;
411                 }
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() + " )";
418                 }
419                 count = db.update(DB_TABLE, values, myWhere, whereArgs);
420                 break;
421             }
422             default: {
423                 if (Config.LOGD) {
424                     Log.d(TAG, "updating unknown/invalid URI: " + uri);
425                 }
426                 throw new UnsupportedOperationException("Cannot update URI: " + uri);
427             }
428         }
429         getContext().getContentResolver().notifyChange(uri, null);
430         return count;
431     }
432
433     /**
434      * Deletes a row in the database
435      */
436     @Override
437     public int delete(final Uri uri, final String where,
438             final String[] whereArgs) {
439         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
440         int count;
441         int match = sURIMatcher.match(uri);
442         switch (match) {
443             case DOWNLOADS:
444             case DOWNLOADS_ID: {
445                 String myWhere;
446                 if (where != null) {
447                     if (match == DOWNLOADS) {
448                         myWhere = where;
449                     } else {
450                         myWhere = where + " AND ";
451                     }
452                 } else {
453                     myWhere = "";
454                 }
455                 if (match == DOWNLOADS_ID) {
456                     String segment = uri.getPathSegments().get(1);
457                     long rowId = Long.parseLong(segment);
458                     myWhere += Downloads._ID + " = " + rowId;
459                 }
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() + " )";
466                 }
467                 count = db.delete(DB_TABLE, myWhere, whereArgs);
468                 break;
469             }
470             default: {
471                 if (Config.LOGD) {
472                     Log.d(TAG, "deleting unknown/invalid URI: " + uri);
473                 }
474                 throw new UnsupportedOperationException("Cannot delete URI: " + uri);
475             }
476         }
477         getContext().getContentResolver().notifyChange(uri, null);
478         return count;
479     }
480
481     /**
482      * Remotely opens a file
483      */
484     @Override
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");
493             } else {
494                 if (!cursor.moveToFirst()) {
495                     Log.v(TAG, "empty cursor in openFile");
496                 } else {
497                     do {
498                         Log.v(TAG, "row " + cursor.getInt(0) + " available");
499                     } while(cursor.moveToNext());
500                 }
501                 cursor.close();
502             }
503             cursor = query(uri, new String[] { "_data" }, null, null, null);
504             if (cursor == null) {
505                 Log.v(TAG, "null cursor in openFile");
506             } else {
507                 if (!cursor.moveToFirst()) {
508                     Log.v(TAG, "empty cursor in openFile");
509                 } else {
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");
514                     }
515                 }
516                cursor.close();
517             }
518         }
519         ParcelFileDescriptor ret = openFileHelper(uri, mode);
520         if (ret == null) {
521             if (Config.LOGD) {
522                 Log.d(TAG, "couldn't open file");
523             }
524         } else {
525             ContentValues values = new ContentValues();
526             values.put(Downloads.LAST_MODIFICATION, System.currentTimeMillis());
527             update(uri, values, null, null);
528         }
529         return ret;
530     }
531
532 }