• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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.drm;
18 
19 import android.content.*;
20 import android.content.pm.PackageManager;
21 import android.database.Cursor;
22 import android.database.sqlite.SQLiteDatabase;
23 import android.database.sqlite.SQLiteException;
24 import android.database.sqlite.SQLiteOpenHelper;
25 import android.database.sqlite.SQLiteQueryBuilder;
26 import android.database.sqlite.SQLiteStatement;
27 import android.net.Uri;
28 import android.os.ParcelFileDescriptor;
29 import android.provider.DrmStore;
30 import android.provider.OpenableColumns;
31 import android.text.TextUtils;
32 import android.util.Log;
33 
34 import java.io.File;
35 import java.io.FileInputStream;
36 import java.io.FileNotFoundException;
37 import java.io.FileOutputStream;
38 import java.io.InputStream;
39 import java.io.IOException;
40 import java.util.HashMap;
41 
42 /**
43  * Drm content provider. See {@link android.provider.DrmStore} for details.
44  *
45  * @hide
46  */
47 public class DrmProvider extends ContentProvider
48 {
49     /**
50      * Creates and updated database on demand when opening it.
51      * Helper class to create database the first time the provider is
52      * initialized and upgrade it when a new version of the provider needs
53      * an updated version of the database.
54      */
55     private final class OpenDatabaseHelper extends SQLiteOpenHelper {
56         private static final String DATABASE_NAME = "drm.db";
57         private static final int DATABASE_VERSION = 1;
58 
OpenDatabaseHelper(Context context)59         OpenDatabaseHelper(Context context) {
60             super(context, DATABASE_NAME, null, DATABASE_VERSION);
61         }
62 
63         /**
64          * Creates database the first time we try to open it.
65          */
66         @Override
onCreate(final SQLiteDatabase db)67         public void onCreate(final SQLiteDatabase db) {
68             createTables(db);
69         }
70 
71         /**
72          * Checks data integrity when opening the database
73          */
74         @Override
onOpen(final SQLiteDatabase db)75         public void onOpen(final SQLiteDatabase db) {
76             super.onOpen(db);
77             // TODO: validate and/or clean up database in onOpen - should that
78             //        be done in the service instead?
79         }
80 
81         /**
82          * Updates the database format when a new content provider is used
83          * with an older database format.
84          */
85         // For now, just deletes the database.
86         // TODO: decide on a general policy when updates become relevant
87         // TODO: can there even be a downgrade? how can it be handled?
88         // TODO: delete temporary files
89         @Override
onUpgrade(final SQLiteDatabase db, final int oldV, final int newV)90         public void onUpgrade(final SQLiteDatabase db,
91                 final int oldV, final int newV) {
92             Log.i(TAG, "Upgrading downloads database from version " +
93                   oldV+ " to " + newV +
94                   ", which will destroy all old data");
95             dropTables(db);
96             createTables(db);
97         }
98     }
99 
100     @Override
onCreate()101     public boolean onCreate()
102     {
103         mOpenHelper = new OpenDatabaseHelper(getContext());
104         return true;
105     }
106 
107     /**
108      * Creates the table that'll hold the download information.
109      */
createTables(SQLiteDatabase db)110     private void createTables(SQLiteDatabase db) {
111         db.execSQL("CREATE TABLE audio (" +
112                    "_id INTEGER PRIMARY KEY," +
113                    "_data TEXT," +
114                    "_size INTEGER," +
115                    "title TEXT," +
116                    "mime_type TEXT" +
117                   ");");
118 
119         db.execSQL("CREATE TABLE images (" +
120                    "_id INTEGER PRIMARY KEY," +
121                    "_data TEXT," +
122                    "_size INTEGER," +
123                    "title TEXT," +
124                    "mime_type TEXT" +
125                   ");");
126     }
127 
128     /**
129      * Deletes the table that holds the download information.
130      */
dropTables(SQLiteDatabase db)131     private void dropTables(SQLiteDatabase db) {
132         // TODO: error handling
133         db.execSQL("DROP TABLE IF EXISTS audio");
134         db.execSQL("DROP TABLE IF EXISTS images");
135     }
136 
137     @Override
query(Uri uri, String[] projectionIn, String selection, String[] selectionArgs, String sort)138     public Cursor query(Uri uri, String[] projectionIn, String selection,
139             String[] selectionArgs, String sort) {
140         String groupBy = null;
141 
142         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
143 
144         switch (URI_MATCHER.match(uri)) {
145             case AUDIO:
146                 qb.setTables("audio");
147                 break;
148 
149             case AUDIO_ID:
150                 qb.setTables("audio");
151                 qb.appendWhere("_id=" + uri.getPathSegments().get(1));
152                 break;
153 
154             case IMAGES:
155                 qb.setTables("images");
156                 break;
157 
158             case IMAGES_ID:
159                 qb.setTables("images");
160                 qb.appendWhere("_id=" + uri.getPathSegments().get(1));
161                 break;
162 
163             default:
164                 throw new IllegalStateException("Unknown URL: " + uri.toString());
165         }
166 
167         if (projectionIn != null) {
168             for (int i = 0; i < projectionIn.length; i++) {
169                 if (projectionIn[i].equals(OpenableColumns.DISPLAY_NAME)) {
170                     projectionIn[i] = "title AS " + OpenableColumns.DISPLAY_NAME;
171                 }
172             }
173         }
174 
175         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
176         Cursor c = qb.query(db, projectionIn, selection,
177                 selectionArgs, groupBy, null, sort);
178         if (c != null) {
179             c.setNotificationUri(getContext().getContentResolver(), uri);
180         }
181         return c;
182     }
183 
184     @Override
getType(Uri url)185     public String getType(Uri url)
186     {
187         switch (URI_MATCHER.match(url)) {
188             case AUDIO_ID:
189             case IMAGES_ID:
190                 Cursor c = query(url, MIME_TYPE_PROJECTION, null, null, null);
191                 if (c != null && c.getCount() == 1) {
192                     c.moveToFirst();
193                     String mimeType = c.getString(1);
194                     c.deactivate();
195                     return mimeType;
196                 }
197                 break;
198         }
199         throw new IllegalStateException("Unknown URL");
200     }
201 
202     /**
203      * Ensures there is a file in the _data column of values, if one isn't
204      * present a new file is created.
205      *
206      * @param initialValues the values passed to insert by the caller
207      * @return the new values
208      */
ensureFile(ContentValues initialValues)209     private ContentValues ensureFile(ContentValues initialValues) {
210         try {
211             File parent = getContext().getFilesDir();
212             parent.mkdirs();
213             File file = File.createTempFile("DRM-", ".data", parent);
214             ContentValues values = new ContentValues(initialValues);
215             values.put("_data", file.toString());
216             return values;
217        } catch (IOException e) {
218             Log.e(TAG, "Failed to create data file in ensureFile");
219             return null;
220        }
221     }
222 
223     @Override
insert(Uri uri, ContentValues initialValues)224     public Uri insert(Uri uri, ContentValues initialValues)
225     {
226         if (getContext().checkCallingOrSelfPermission(Manifest.permission.INSTALL_DRM)
227                 != PackageManager.PERMISSION_GRANTED) {
228             throw new SecurityException("Requires INSTALL_DRM permission");
229         }
230 
231         long rowId;
232         int match = URI_MATCHER.match(uri);
233         Uri newUri = null;
234         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
235 
236         if (initialValues == null) {
237             initialValues = new ContentValues();
238         }
239 
240         switch (match) {
241             case AUDIO: {
242                 ContentValues values = ensureFile(initialValues);
243                 if (values == null) return null;
244                 rowId = db.insert("audio", "title", values);
245                 if (rowId > 0) {
246                     newUri = ContentUris.withAppendedId(DrmStore.Audio.CONTENT_URI, rowId);
247                 }
248                 break;
249             }
250 
251             case IMAGES: {
252                 ContentValues values = ensureFile(initialValues);
253                 if (values == null) return null;
254                 rowId = db.insert("images", "title", values);
255                 if (rowId > 0) {
256                     newUri = ContentUris.withAppendedId(DrmStore.Images.CONTENT_URI, rowId);
257                 }
258                 break;
259             }
260 
261             default:
262                 throw new UnsupportedOperationException("Invalid URI " + uri);
263         }
264 
265         if (newUri != null) {
266             getContext().getContentResolver().notifyChange(uri, null);
267         }
268 
269         return newUri;
270     }
271 
272     private static final class GetTableAndWhereOutParameter {
273         public String table;
274         public String where;
275     }
276 
277     static final GetTableAndWhereOutParameter sGetTableAndWhereParam =
278             new GetTableAndWhereOutParameter();
279 
getTableAndWhere(Uri uri, int match, String userWhere, GetTableAndWhereOutParameter out)280     private void getTableAndWhere(Uri uri, int match, String userWhere,
281             GetTableAndWhereOutParameter out) {
282         String where = null;
283         switch (match) {
284             case AUDIO:
285                 out.table = "audio";
286                 break;
287 
288             case AUDIO_ID:
289                 out.table = "audio";
290                 where = "_id=" + uri.getPathSegments().get(1);
291                 break;
292 
293             case IMAGES:
294                 out.table = "images";
295                 break;
296 
297             case IMAGES_ID:
298                 out.table = "images";
299                 where = "_id=" + uri.getPathSegments().get(1);
300                 break;
301 
302             default:
303                 throw new UnsupportedOperationException(
304                         "Unknown or unsupported URL: " + uri.toString());
305         }
306 
307         // Add in the user requested WHERE clause, if needed
308         if (!TextUtils.isEmpty(userWhere)) {
309             if (!TextUtils.isEmpty(where)) {
310                 out.where = where + " AND (" + userWhere + ")";
311             } else {
312                 out.where = userWhere;
313             }
314         } else {
315             out.where = where;
316         }
317     }
318 
deleteFiles(Uri uri, String userWhere, String[] whereArgs)319     private void deleteFiles(Uri uri, String userWhere, String[] whereArgs) {
320         Cursor c = query(uri, new String [] { "_data" }, userWhere, whereArgs, null);
321 
322         try {
323             if (c != null && c.moveToFirst()) {
324                 String prefix = getContext().getFilesDir().getPath();
325                 do {
326                     String path = c.getString(0);
327                     if (!path.startsWith(prefix)) {
328                         throw new SecurityException("Attempted to delete a non-DRM file");
329                     }
330                     new File(path).delete();
331                 } while (c.moveToNext());
332             }
333         } finally {
334             if (c != null) {
335                 c.close();
336             }
337         }
338     }
339 
340     @Override
delete(Uri uri, String userWhere, String[] whereArgs)341     public int delete(Uri uri, String userWhere, String[] whereArgs) {
342         if (getContext().checkCallingOrSelfPermission(Manifest.permission.ACCESS_DRM)
343                 != PackageManager.PERMISSION_GRANTED) {
344             throw new SecurityException("Requires DRM permission");
345         }
346 
347         int count;
348         int match = URI_MATCHER.match(uri);
349         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
350 
351         synchronized (sGetTableAndWhereParam) {
352             getTableAndWhere(uri, match, userWhere, sGetTableAndWhereParam);
353             switch (match) {
354                 default:
355                     deleteFiles(uri, userWhere, whereArgs);
356                     count = db.delete(sGetTableAndWhereParam.table,
357                             sGetTableAndWhereParam.where, whereArgs);
358                     break;
359             }
360         }
361 
362         return count;
363     }
364 
365     @Override
update(Uri uri, ContentValues initialValues, String userWhere, String[] whereArgs)366     public int update(Uri uri, ContentValues initialValues, String userWhere,
367             String[] whereArgs) {
368         if (getContext().checkCallingOrSelfPermission(Manifest.permission.ACCESS_DRM)
369                 != PackageManager.PERMISSION_GRANTED) {
370             throw new SecurityException("Requires ACCESS_DRM permission");
371         }
372 
373         int count;
374         int match = URI_MATCHER.match(uri);
375         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
376 
377         synchronized (sGetTableAndWhereParam) {
378             getTableAndWhere(uri, match, userWhere, sGetTableAndWhereParam);
379 
380             switch (match) {
381                 default:
382                     count = db.update(sGetTableAndWhereParam.table, initialValues,
383                         sGetTableAndWhereParam.where, whereArgs);
384                     break;
385             }
386         }
387 
388         return count;
389     }
390 
391     @Override
openFile(Uri uri, String mode)392     public ParcelFileDescriptor openFile(Uri uri, String mode)
393             throws FileNotFoundException {
394         String requiredPermission = mode.equals("w") ?
395                 Manifest.permission.INSTALL_DRM : Manifest.permission.ACCESS_DRM;
396 
397         if (getContext().checkCallingOrSelfPermission(requiredPermission)
398                 != PackageManager.PERMISSION_GRANTED) {
399             throw new SecurityException("Requires " + requiredPermission);
400         }
401         return openFileHelper(uri, mode);
402     }
403 
404     private static String TAG = "DrmProvider";
405 
406     private static final int AUDIO = 100;
407     private static final int AUDIO_ID = 101;
408     private static final int IMAGES = 102;
409     private static final int IMAGES_ID = 103;
410 
411     private static final UriMatcher URI_MATCHER =
412             new UriMatcher(UriMatcher.NO_MATCH);
413 
414     private static final String[] MIME_TYPE_PROJECTION = new String[] {
415             DrmStore.Columns._ID, // 0
416             DrmStore.Columns.MIME_TYPE, // 1
417     };
418 
419     private SQLiteOpenHelper mOpenHelper;
420 
421     static
422     {
URI_MATCHER.addURI(DrmStore.AUTHORITY, "audio", AUDIO)423         URI_MATCHER.addURI(DrmStore.AUTHORITY, "audio", AUDIO);
URI_MATCHER.addURI(DrmStore.AUTHORITY, "audio/#", AUDIO_ID)424         URI_MATCHER.addURI(DrmStore.AUTHORITY, "audio/#", AUDIO_ID);
URI_MATCHER.addURI(DrmStore.AUTHORITY, "images", IMAGES)425         URI_MATCHER.addURI(DrmStore.AUTHORITY, "images", IMAGES);
URI_MATCHER.addURI(DrmStore.AUTHORITY, "images/#", IMAGES_ID)426         URI_MATCHER.addURI(DrmStore.AUTHORITY, "images/#", IMAGES_ID);
427     }
428 }
429