Home | History | Annotate | Download | only in threadsample
      1 /*
      2  * Copyright (C) 2012 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.example.android.threadsample;
     18 
     19 import android.content.ContentProvider;
     20 import android.content.ContentValues;
     21 import android.content.Context;
     22 import android.content.UriMatcher;
     23 import android.database.Cursor;
     24 import android.database.sqlite.SQLiteDatabase;
     25 import android.database.sqlite.SQLiteException;
     26 import android.database.sqlite.SQLiteOpenHelper;
     27 import android.net.Uri;
     28 import android.util.Log;
     29 import android.util.SparseArray;
     30 
     31 /**
     32  *
     33  * Defines a ContentProvider that stores URLs of Picasa featured pictures
     34  * The provider also has a table that tracks the last time a picture URL was updated.
     35  */
     36 public class DataProvider extends ContentProvider {
     37     // Indicates that the incoming query is for a picture URL
     38     public static final int IMAGE_URL_QUERY = 1;
     39 
     40     // Indicates that the incoming query is for a URL modification date
     41     public static final int URL_DATE_QUERY = 2;
     42 
     43     // Indicates an invalid content URI
     44     public static final int INVALID_URI = -1;
     45 
     46     // Constants for building SQLite tables during initialization
     47     private static final String TEXT_TYPE = "TEXT";
     48     private static final String PRIMARY_KEY_TYPE = "INTEGER PRIMARY KEY";
     49     private static final String INTEGER_TYPE = "INTEGER";
     50 
     51     // Defines an SQLite statement that builds the Picasa picture URL table
     52     private static final String CREATE_PICTUREURL_TABLE_SQL = "CREATE TABLE" + " " +
     53             DataProviderContract.PICTUREURL_TABLE_NAME + " " +
     54             "(" + " " +
     55             DataProviderContract.ROW_ID + " " + PRIMARY_KEY_TYPE + " ," +
     56             DataProviderContract.IMAGE_THUMBURL_COLUMN + " " + TEXT_TYPE + " ," +
     57             DataProviderContract.IMAGE_URL_COLUMN + " " + TEXT_TYPE + " ," +
     58             DataProviderContract.IMAGE_THUMBNAME_COLUMN + " " + TEXT_TYPE + " ," +
     59             DataProviderContract.IMAGE_PICTURENAME_COLUMN + " " + TEXT_TYPE +
     60             ")";
     61 
     62     // Defines an SQLite statement that builds the URL modification date table
     63     private static final String CREATE_DATE_TABLE_SQL = "CREATE TABLE" + " " +
     64             DataProviderContract.DATE_TABLE_NAME + " " +
     65             "(" + " " +
     66             DataProviderContract.ROW_ID + " " + PRIMARY_KEY_TYPE + " ," +
     67             DataProviderContract.DATA_DATE_COLUMN + " " + INTEGER_TYPE +
     68             ")";
     69 
     70     // Identifies log statements issued by this component
     71     public static final String LOG_TAG = "DataProvider";
     72 
     73     // Defines an helper object for the backing database
     74     private SQLiteOpenHelper mHelper;
     75 
     76     // Defines a helper object that matches content URIs to table-specific parameters
     77     private static final UriMatcher sUriMatcher;
     78 
     79     // Stores the MIME types served by this provider
     80     private static final SparseArray<String> sMimeTypes;
     81 
     82     /*
     83      * Initializes meta-data used by the content provider:
     84      * - UriMatcher that maps content URIs to codes
     85      * - MimeType array that returns the custom MIME type of a table
     86      */
     87     static {
     88 
     89         // Creates an object that associates content URIs with numeric codes
     90         sUriMatcher = new UriMatcher(0);
     91 
     92         /*
     93          * Sets up an array that maps content URIs to MIME types, via a mapping between the
     94          * URIs and an integer code. These are custom MIME types that apply to tables and rows
     95          * in this particular provider.
     96          */
     97         sMimeTypes = new SparseArray<String>();
     98 
     99         // Adds a URI "match" entry that maps picture URL content URIs to a numeric code
    100         sUriMatcher.addURI(
    101                 DataProviderContract.AUTHORITY,
    102                 DataProviderContract.PICTUREURL_TABLE_NAME,
    103                 IMAGE_URL_QUERY);
    104 
    105         // Adds a URI "match" entry that maps modification date content URIs to a numeric code
    106         sUriMatcher.addURI(
    107             DataProviderContract.AUTHORITY,
    108             DataProviderContract.DATE_TABLE_NAME,
    109             URL_DATE_QUERY);
    110 
    111         // Specifies a custom MIME type for the picture URL table
    112         sMimeTypes.put(
    113                 IMAGE_URL_QUERY,
    114                 "vnd.android.cursor.dir/vnd." +
    115                 DataProviderContract.AUTHORITY + "." +
    116                 DataProviderContract.PICTUREURL_TABLE_NAME);
    117 
    118         // Specifies the custom MIME type for a single modification date row
    119         sMimeTypes.put(
    120                 URL_DATE_QUERY,
    121                 "vnd.android.cursor.item/vnd."+
    122                 DataProviderContract.AUTHORITY + "." +
    123                 DataProviderContract.DATE_TABLE_NAME);
    124     }
    125 
    126     // Closes the SQLite database helper class, to avoid memory leaks
    127     public void close() {
    128         mHelper.close();
    129     }
    130 
    131     /**
    132      * Defines a helper class that opens the SQLite database for this provider when a request is
    133      * received. If the database doesn't yet exist, the helper creates it.
    134      */
    135     private class DataProviderHelper extends SQLiteOpenHelper {
    136         /**
    137          * Instantiates a new SQLite database using the supplied database name and version
    138          *
    139          * @param context The current context
    140          */
    141         DataProviderHelper(Context context) {
    142             super(context,
    143                     DataProviderContract.DATABASE_NAME,
    144                     null,
    145                     DataProviderContract.DATABASE_VERSION);
    146         }
    147 
    148 
    149         /**
    150          * Executes the queries to drop all of the tables from the database.
    151          *
    152          * @param db A handle to the provider's backing database.
    153          */
    154         private void dropTables(SQLiteDatabase db) {
    155 
    156             // If the table doesn't exist, don't throw an error
    157             db.execSQL("DROP TABLE IF EXISTS " + DataProviderContract.PICTUREURL_TABLE_NAME);
    158             db.execSQL("DROP TABLE IF EXISTS " + DataProviderContract.DATE_TABLE_NAME);
    159         }
    160 
    161         /**
    162          * Does setup of the database. The system automatically invokes this method when
    163          * SQLiteDatabase.getWriteableDatabase() or SQLiteDatabase.getReadableDatabase() are
    164          * invoked and no db instance is available.
    165          *
    166          * @param db the database instance in which to create the tables.
    167          */
    168         @Override
    169         public void onCreate(SQLiteDatabase db) {
    170             // Creates the tables in the backing database for this provider
    171             db.execSQL(CREATE_PICTUREURL_TABLE_SQL);
    172             db.execSQL(CREATE_DATE_TABLE_SQL);
    173 
    174         }
    175 
    176         /**
    177          * Handles upgrading the database from a previous version. Drops the old tables and creates
    178          * new ones.
    179          *
    180          * @param db The database to upgrade
    181          * @param version1 The old database version
    182          * @param version2 The new database version
    183          */
    184         @Override
    185         public void onUpgrade(SQLiteDatabase db, int version1, int version2) {
    186             Log.w(DataProviderHelper.class.getName(),
    187                     "Upgrading database from version " + version1 + " to "
    188                             + version2 + ", which will destroy all the existing data");
    189 
    190             // Drops all the existing tables in the database
    191             dropTables(db);
    192 
    193             // Invokes the onCreate callback to build new tables
    194             onCreate(db);
    195         }
    196         /**
    197          * Handles downgrading the database from a new to a previous version. Drops the old tables
    198          * and creates new ones.
    199          * @param db The database object to downgrade
    200          * @param version1 The old database version
    201          * @param version2 The new database version
    202          */
    203         @Override
    204         public void onDowngrade(SQLiteDatabase db, int version1, int version2) {
    205             Log.w(DataProviderHelper.class.getName(),
    206                 "Downgrading database from version " + version1 + " to "
    207                         + version2 + ", which will destroy all the existing data");
    208 
    209             // Drops all the existing tables in the database
    210             dropTables(db);
    211 
    212             // Invokes the onCreate callback to build new tables
    213             onCreate(db);
    214 
    215         }
    216     }
    217     /**
    218      * Initializes the content provider. Notice that this method simply creates a
    219      * the SQLiteOpenHelper instance and returns. You should do most of the initialization of a
    220      * content provider in its static initialization block or in SQLiteDatabase.onCreate().
    221      */
    222     @Override
    223     public boolean onCreate() {
    224 
    225         // Creates a new database helper object
    226         mHelper = new DataProviderHelper(getContext());
    227 
    228         return true;
    229     }
    230     /**
    231      * Returns the result of querying the chosen table.
    232      * @see android.content.ContentProvider#query(Uri, String[], String, String[], String)
    233      * @param uri The content URI of the table
    234      * @param projection The names of the columns to return in the cursor
    235      * @param selection The selection clause for the query
    236      * @param selectionArgs An array of Strings containing search criteria
    237      * @param sortOrder A clause defining the order in which the retrieved rows should be sorted
    238      * @return The query results, as a {@link android.database.Cursor} of rows and columns
    239      */
    240     @Override
    241     public Cursor query(
    242         Uri uri,
    243         String[] projection,
    244         String selection,
    245         String[] selectionArgs,
    246         String sortOrder) {
    247 
    248         SQLiteDatabase db = mHelper.getReadableDatabase();
    249         // Decodes the content URI and maps it to a code
    250         switch (sUriMatcher.match(uri)) {
    251 
    252             // If the query is for a picture URL
    253             case IMAGE_URL_QUERY:
    254                 // Does the query against a read-only version of the database
    255                 Cursor returnCursor = db.query(
    256                     DataProviderContract.PICTUREURL_TABLE_NAME,
    257                     projection,
    258                     null, null, null, null, null);
    259 
    260                 // Sets the ContentResolver to watch this content URI for data changes
    261                 returnCursor.setNotificationUri(getContext().getContentResolver(), uri);
    262                 return returnCursor;
    263 
    264             // If the query is for a modification date URL
    265             case URL_DATE_QUERY:
    266                 returnCursor = db.query(
    267                     DataProviderContract.DATE_TABLE_NAME,
    268                     projection,
    269                     selection,
    270                     selectionArgs,
    271                     null,
    272                     null,
    273                     sortOrder);
    274 
    275                 // No notification Uri is set, because the data doesn't have to be watched.
    276                 return returnCursor;
    277 
    278             case INVALID_URI:
    279 
    280                 throw new IllegalArgumentException("Query -- Invalid URI:" + uri);
    281         }
    282 
    283         return null;
    284     }
    285 
    286     /**
    287      * Returns the mimeType associated with the Uri (query).
    288      * @see android.content.ContentProvider#getType(Uri)
    289      * @param uri the content URI to be checked
    290      * @return the corresponding MIMEtype
    291      */
    292     @Override
    293     public String getType(Uri uri) {
    294 
    295         return sMimeTypes.get(sUriMatcher.match(uri));
    296     }
    297     /**
    298      *
    299      * Insert a single row into a table
    300      * @see android.content.ContentProvider#insert(Uri, ContentValues)
    301      * @param uri the content URI of the table
    302      * @param values a {@link android.content.ContentValues} object containing the row to insert
    303      * @return the content URI of the new row
    304      */
    305     @Override
    306     public Uri insert(Uri uri, ContentValues values) {
    307 
    308         // Decode the URI to choose which action to take
    309         switch (sUriMatcher.match(uri)) {
    310 
    311             // For the modification date table
    312             case URL_DATE_QUERY:
    313 
    314                 // Creates a writeable database or gets one from cache
    315                 SQLiteDatabase localSQLiteDatabase = mHelper.getWritableDatabase();
    316 
    317                 // Inserts the row into the table and returns the new row's _id value
    318                 long id = localSQLiteDatabase.insert(
    319                         DataProviderContract.DATE_TABLE_NAME,
    320                         DataProviderContract.DATA_DATE_COLUMN,
    321                         values
    322                 );
    323 
    324                 // If the insert succeeded, notify a change and return the new row's content URI.
    325                 if (-1 != id) {
    326                     getContext().getContentResolver().notifyChange(uri, null);
    327                     return Uri.withAppendedPath(uri, Long.toString(id));
    328                 } else {
    329 
    330                     throw new SQLiteException("Insert error:" + uri);
    331                 }
    332             case IMAGE_URL_QUERY:
    333 
    334                 throw new IllegalArgumentException("Insert: Invalid URI" + uri);
    335         }
    336 
    337         return null;
    338     }
    339     /**
    340      * Implements bulk row insertion using
    341      * {@link SQLiteDatabase#insert(String, String, ContentValues) SQLiteDatabase.insert()}
    342      * and SQLite transactions. The method also notifies the current
    343      * {@link android.content.ContentResolver} that the {@link android.content.ContentProvider} has
    344      * been changed.
    345      * @see android.content.ContentProvider#bulkInsert(Uri, ContentValues[])
    346      * @param uri The content URI for the insertion
    347      * @param insertValuesArray A {@link android.content.ContentValues} array containing the row to
    348      * insert
    349      * @return The number of rows inserted.
    350      */
    351     @Override
    352     public int bulkInsert(Uri uri, ContentValues[] insertValuesArray) {
    353 
    354         // Decodes the content URI and choose which insert to use
    355         switch (sUriMatcher.match(uri)) {
    356 
    357             // picture URLs table
    358             case IMAGE_URL_QUERY:
    359 
    360                 // Gets a writeable database instance if one is not already cached
    361                 SQLiteDatabase localSQLiteDatabase = mHelper.getWritableDatabase();
    362 
    363                 /*
    364                  * Begins a transaction in "exclusive" mode. No other mutations can occur on the
    365                  * db until this transaction finishes.
    366                  */
    367                 localSQLiteDatabase.beginTransaction();
    368 
    369                 // Deletes all the existing rows in the table
    370                 localSQLiteDatabase.delete(DataProviderContract.PICTUREURL_TABLE_NAME, null, null);
    371 
    372                 // Gets the size of the bulk insert
    373                 int numImages = insertValuesArray.length;
    374 
    375                 // Inserts each ContentValues entry in the array as a row in the database
    376                 for (int i = 0; i < numImages; i++) {
    377 
    378                     localSQLiteDatabase.insert(DataProviderContract.PICTUREURL_TABLE_NAME,
    379                             DataProviderContract.IMAGE_URL_COLUMN, insertValuesArray[i]);
    380                 }
    381 
    382                 // Reports that the transaction was successful and should not be backed out.
    383                 localSQLiteDatabase.setTransactionSuccessful();
    384 
    385                 // Ends the transaction and closes the current db instances
    386                 localSQLiteDatabase.endTransaction();
    387                 localSQLiteDatabase.close();
    388 
    389                 /*
    390                  * Notifies the current ContentResolver that the data associated with "uri" has
    391                  * changed.
    392                  */
    393 
    394                 getContext().getContentResolver().notifyChange(uri, null);
    395 
    396                 // The semantics of bulkInsert is to return the number of rows inserted.
    397                 return numImages;
    398 
    399             // modification date table
    400             case URL_DATE_QUERY:
    401 
    402                 // Do inserts by calling SQLiteDatabase.insert on each row in insertValuesArray
    403                 return super.bulkInsert(uri, insertValuesArray);
    404 
    405             case INVALID_URI:
    406 
    407                 // An invalid URI was passed. Throw an exception
    408                 throw new IllegalArgumentException("Bulk insert -- Invalid URI:" + uri);
    409 
    410         }
    411 
    412         return -1;
    413 
    414     }
    415     /**
    416      * Returns an UnsupportedOperationException if delete is called
    417      * @see android.content.ContentProvider#delete(Uri, String, String[])
    418      * @param uri The content URI
    419      * @param selection The SQL WHERE string. Use "?" to mark places that should be substituted by
    420      * values in selectionArgs.
    421      * @param selectionArgs An array of values that are mapped to each "?" in selection. If no "?"
    422      * are used, set this to NULL.
    423      *
    424      * @return the number of rows deleted
    425      */
    426     @Override
    427     public int delete(Uri uri, String selection, String[] selectionArgs) {
    428 
    429         throw new UnsupportedOperationException("Delete -- unsupported operation " + uri);
    430     }
    431 
    432     /**
    433      * Updates one or more rows in a table.
    434      * @see android.content.ContentProvider#update(Uri, ContentValues, String, String[])
    435      * @param uri The content URI for the table
    436      * @param values The values to use to update the row or rows. You only need to specify column
    437      * names for the columns you want to change. To clear the contents of a column, specify the
    438      * column name and NULL for its value.
    439      * @param selection An SQL WHERE clause (without the WHERE keyword) specifying the rows to
    440      * update. Use "?" to mark places that should be substituted by values in selectionArgs.
    441      * @param selectionArgs An array of values that are mapped in order to each "?" in selection.
    442      * If no "?" are used, set this to NULL.
    443      *
    444      * @return int The number of rows updated.
    445      */
    446     @Override
    447     public int update(Uri uri, ContentValues values, String selection,
    448             String[] selectionArgs) {
    449 
    450         // Decodes the content URI and choose which insert to use
    451         switch (sUriMatcher.match(uri)) {
    452 
    453             // A picture URL content URI
    454             case URL_DATE_QUERY:
    455 
    456                 // Creats a new writeable database or retrieves a cached one
    457                 SQLiteDatabase localSQLiteDatabase = mHelper.getWritableDatabase();
    458 
    459                 // Updates the table
    460                 int rows = localSQLiteDatabase.update(
    461                         DataProviderContract.DATE_TABLE_NAME,
    462                         values,
    463                         selection,
    464                         selectionArgs);
    465 
    466                 // If the update succeeded, notify a change and return the number of updated rows.
    467                 if (0 != rows) {
    468                     getContext().getContentResolver().notifyChange(uri, null);
    469                     return rows;
    470                 } else {
    471 
    472                     throw new SQLiteException("Update error:" + uri);
    473                 }
    474 
    475             case IMAGE_URL_QUERY:
    476 
    477                 throw new IllegalArgumentException("Update: Invalid URI: " + uri);
    478         }
    479 
    480         return -1;
    481     }
    482 }
    483