Home | History | Annotate | Download | only in notepad
      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.example.android.notepad;
     18 
     19 import com.example.android.notepad.NotePad;
     20 
     21 import android.content.ClipDescription;
     22 import android.content.ContentProvider;
     23 import android.content.ContentUris;
     24 import android.content.ContentValues;
     25 import android.content.Context;
     26 import android.content.UriMatcher;
     27 import android.content.ContentProvider.PipeDataWriter;
     28 import android.content.res.AssetFileDescriptor;
     29 import android.content.res.Resources;
     30 import android.database.Cursor;
     31 import android.database.SQLException;
     32 import android.database.sqlite.SQLiteDatabase;
     33 import android.database.sqlite.SQLiteOpenHelper;
     34 import android.database.sqlite.SQLiteQueryBuilder;
     35 import android.net.Uri;
     36 import android.os.Bundle;
     37 import android.os.ParcelFileDescriptor;
     38 import android.provider.LiveFolders;
     39 import android.text.TextUtils;
     40 import android.util.Log;
     41 
     42 import java.io.FileNotFoundException;
     43 import java.io.FileOutputStream;
     44 import java.io.IOException;
     45 import java.io.OutputStreamWriter;
     46 import java.io.PrintWriter;
     47 import java.io.UnsupportedEncodingException;
     48 import java.util.HashMap;
     49 
     50 /**
     51  * Provides access to a database of notes. Each note has a title, the note
     52  * itself, a creation date and a modified data.
     53  */
     54 public class NotePadProvider extends ContentProvider implements PipeDataWriter<Cursor> {
     55     // Used for debugging and logging
     56     private static final String TAG = "NotePadProvider";
     57 
     58     /**
     59      * The database that the provider uses as its underlying data store
     60      */
     61     private static final String DATABASE_NAME = "note_pad.db";
     62 
     63     /**
     64      * The database version
     65      */
     66     private static final int DATABASE_VERSION = 2;
     67 
     68     /**
     69      * A projection map used to select columns from the database
     70      */
     71     private static HashMap<String, String> sNotesProjectionMap;
     72 
     73     /**
     74      * Standard projection for the interesting columns of a normal note.
     75      */
     76     private static final String[] READ_NOTE_PROJECTION = new String[] {
     77             NotePad.Notes._ID,               // Projection position 0, the note's id
     78             NotePad.Notes.COLUMN_NAME_NOTE,  // Projection position 1, the note's content
     79             NotePad.Notes.COLUMN_NAME_TITLE, // Projection position 2, the note's title
     80     };
     81     private static final int READ_NOTE_NOTE_INDEX = 1;
     82     private static final int READ_NOTE_TITLE_INDEX = 2;
     83 
     84     /*
     85      * Constants used by the Uri matcher to choose an action based on the pattern
     86      * of the incoming URI
     87      */
     88     // The incoming URI matches the Notes URI pattern
     89     private static final int NOTES = 1;
     90 
     91     // The incoming URI matches the Note ID URI pattern
     92     private static final int NOTE_ID = 2;
     93 
     94     /**
     95      * A UriMatcher instance
     96      */
     97     private static final UriMatcher sUriMatcher;
     98 
     99     // Handle to a new DatabaseHelper.
    100     private DatabaseHelper mOpenHelper;
    101 
    102 
    103     /**
    104      * A block that instantiates and sets static objects
    105      */
    106     static {
    107 
    108         /*
    109          * Creates and initializes the URI matcher
    110          */
    111         // Create a new instance
    112         sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    113 
    114         // Add a pattern that routes URIs terminated with "notes" to a NOTES operation
    115         sUriMatcher.addURI(NotePad.AUTHORITY, "notes", NOTES);
    116 
    117         // Add a pattern that routes URIs terminated with "notes" plus an integer
    118         // to a note ID operation
    119         sUriMatcher.addURI(NotePad.AUTHORITY, "notes/#", NOTE_ID);
    120 
    121         /*
    122          * Creates and initializes a projection map that returns all columns
    123          */
    124 
    125         // Creates a new projection map instance. The map returns a column name
    126         // given a string. The two are usually equal.
    127         sNotesProjectionMap = new HashMap<String, String>();
    128 
    129         // Maps the string "_ID" to the column name "_ID"
    130         sNotesProjectionMap.put(NotePad.Notes._ID, NotePad.Notes._ID);
    131 
    132         // Maps "title" to "title"
    133         sNotesProjectionMap.put(NotePad.Notes.COLUMN_NAME_TITLE, NotePad.Notes.COLUMN_NAME_TITLE);
    134 
    135         // Maps "note" to "note"
    136         sNotesProjectionMap.put(NotePad.Notes.COLUMN_NAME_NOTE, NotePad.Notes.COLUMN_NAME_NOTE);
    137 
    138         // Maps "created" to "created"
    139         sNotesProjectionMap.put(NotePad.Notes.COLUMN_NAME_CREATE_DATE,
    140                 NotePad.Notes.COLUMN_NAME_CREATE_DATE);
    141 
    142         // Maps "modified" to "modified"
    143         sNotesProjectionMap.put(
    144                 NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE,
    145                 NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE);
    146     }
    147 
    148     /**
    149     *
    150     * This class helps open, create, and upgrade the database file. Set to package visibility
    151     * for testing purposes.
    152     */
    153    static class DatabaseHelper extends SQLiteOpenHelper {
    154 
    155        DatabaseHelper(Context context) {
    156 
    157            // calls the super constructor, requesting the default cursor factory.
    158            super(context, DATABASE_NAME, null, DATABASE_VERSION);
    159        }
    160 
    161        /**
    162         *
    163         * Creates the underlying database with table name and column names taken from the
    164         * NotePad class.
    165         */
    166        @Override
    167        public void onCreate(SQLiteDatabase db) {
    168            db.execSQL("CREATE TABLE " + NotePad.Notes.TABLE_NAME + " ("
    169                    + NotePad.Notes._ID + " INTEGER PRIMARY KEY,"
    170                    + NotePad.Notes.COLUMN_NAME_TITLE + " TEXT,"
    171                    + NotePad.Notes.COLUMN_NAME_NOTE + " TEXT,"
    172                    + NotePad.Notes.COLUMN_NAME_CREATE_DATE + " INTEGER,"
    173                    + NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE + " INTEGER"
    174                    + ");");
    175        }
    176 
    177        /**
    178         *
    179         * Demonstrates that the provider must consider what happens when the
    180         * underlying datastore is changed. In this sample, the database is upgraded the database
    181         * by destroying the existing data.
    182         * A real application should upgrade the database in place.
    183         */
    184        @Override
    185        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    186 
    187            // Logs that the database is being upgraded
    188            Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
    189                    + newVersion + ", which will destroy all old data");
    190 
    191            // Kills the table and existing data
    192            db.execSQL("DROP TABLE IF EXISTS notes");
    193 
    194            // Recreates the database with a new version
    195            onCreate(db);
    196        }
    197    }
    198 
    199    /**
    200     *
    201     * Initializes the provider by creating a new DatabaseHelper. onCreate() is called
    202     * automatically when Android creates the provider in response to a resolver request from a
    203     * client.
    204     */
    205    @Override
    206    public boolean onCreate() {
    207 
    208        // Creates a new helper object. Note that the database itself isn't opened until
    209        // something tries to access it, and it's only created if it doesn't already exist.
    210        mOpenHelper = new DatabaseHelper(getContext());
    211 
    212        // Assumes that any failures will be reported by a thrown exception.
    213        return true;
    214    }
    215 
    216    /**
    217     * This method is called when a client calls
    218     * {@link android.content.ContentResolver#query(Uri, String[], String, String[], String)}.
    219     * Queries the database and returns a cursor containing the results.
    220     *
    221     * @return A cursor containing the results of the query. The cursor exists but is empty if
    222     * the query returns no results or an exception occurs.
    223     * @throws IllegalArgumentException if the incoming URI pattern is invalid.
    224     */
    225    @Override
    226    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
    227            String sortOrder) {
    228 
    229        // Constructs a new query builder and sets its table name
    230        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
    231        qb.setTables(NotePad.Notes.TABLE_NAME);
    232 
    233        /**
    234         * Choose the projection and adjust the "where" clause based on URI pattern-matching.
    235         */
    236        switch (sUriMatcher.match(uri)) {
    237            // If the incoming URI is for notes, chooses the Notes projection
    238            case NOTES:
    239                qb.setProjectionMap(sNotesProjectionMap);
    240                break;
    241 
    242            /* If the incoming URI is for a single note identified by its ID, chooses the
    243             * note ID projection, and appends "_ID = <noteID>" to the where clause, so that
    244             * it selects that single note
    245             */
    246            case NOTE_ID:
    247                qb.setProjectionMap(sNotesProjectionMap);
    248                qb.appendWhere(
    249                    NotePad.Notes._ID +    // the name of the ID column
    250                    "=" +
    251                    // the position of the note ID itself in the incoming URI
    252                    uri.getPathSegments().get(NotePad.Notes.NOTE_ID_PATH_POSITION));
    253                break;
    254 
    255            default:
    256                // If the URI doesn't match any of the known patterns, throw an exception.
    257                throw new IllegalArgumentException("Unknown URI " + uri);
    258        }
    259 
    260 
    261        String orderBy;
    262        // If no sort order is specified, uses the default
    263        if (TextUtils.isEmpty(sortOrder)) {
    264            orderBy = NotePad.Notes.DEFAULT_SORT_ORDER;
    265        } else {
    266            // otherwise, uses the incoming sort order
    267            orderBy = sortOrder;
    268        }
    269 
    270        // Opens the database object in "read" mode, since no writes need to be done.
    271        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    272 
    273        /*
    274         * Performs the query. If no problems occur trying to read the database, then a Cursor
    275         * object is returned; otherwise, the cursor variable contains null. If no records were
    276         * selected, then the Cursor object is empty, and Cursor.getCount() returns 0.
    277         */
    278        Cursor c = qb.query(
    279            db,            // The database to query
    280            projection,    // The columns to return from the query
    281            selection,     // The columns for the where clause
    282            selectionArgs, // The values for the where clause
    283            null,          // don't group the rows
    284            null,          // don't filter by row groups
    285            orderBy        // The sort order
    286        );
    287 
    288        // Tells the Cursor what URI to watch, so it knows when its source data changes
    289        c.setNotificationUri(getContext().getContentResolver(), uri);
    290        return c;
    291    }
    292 
    293    /**
    294     * This is called when a client calls {@link android.content.ContentResolver#getType(Uri)}.
    295     * Returns the MIME data type of the URI given as a parameter.
    296     *
    297     * @param uri The URI whose MIME type is desired.
    298     * @return The MIME type of the URI.
    299     * @throws IllegalArgumentException if the incoming URI pattern is invalid.
    300     */
    301    @Override
    302    public String getType(Uri uri) {
    303 
    304        /**
    305         * Chooses the MIME type based on the incoming URI pattern
    306         */
    307        switch (sUriMatcher.match(uri)) {
    308 
    309            // If the pattern is for notes or live folders, returns the general content type.
    310            case NOTES:
    311                return NotePad.Notes.CONTENT_TYPE;
    312 
    313            // If the pattern is for note IDs, returns the note ID content type.
    314            case NOTE_ID:
    315                return NotePad.Notes.CONTENT_ITEM_TYPE;
    316 
    317            // If the URI pattern doesn't match any permitted patterns, throws an exception.
    318            default:
    319                throw new IllegalArgumentException("Unknown URI " + uri);
    320        }
    321     }
    322 
    323 //BEGIN_INCLUDE(stream)
    324     /**
    325      * This describes the MIME types that are supported for opening a note
    326      * URI as a stream.
    327      */
    328     static ClipDescription NOTE_STREAM_TYPES = new ClipDescription(null,
    329             new String[] { ClipDescription.MIMETYPE_TEXT_PLAIN });
    330 
    331     /**
    332      * Returns the types of available data streams.  URIs to specific notes are supported.
    333      * The application can convert such a note to a plain text stream.
    334      *
    335      * @param uri the URI to analyze
    336      * @param mimeTypeFilter The MIME type to check for. This method only returns a data stream
    337      * type for MIME types that match the filter. Currently, only text/plain MIME types match.
    338      * @return a data stream MIME type. Currently, only text/plan is returned.
    339      * @throws IllegalArgumentException if the URI pattern doesn't match any supported patterns.
    340      */
    341     @Override
    342     public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
    343         /**
    344          *  Chooses the data stream type based on the incoming URI pattern.
    345          */
    346         switch (sUriMatcher.match(uri)) {
    347 
    348             // If the pattern is for notes or live folders, return null. Data streams are not
    349             // supported for this type of URI.
    350             case NOTES:
    351                 return null;
    352 
    353             // If the pattern is for note IDs and the MIME filter is text/plain, then return
    354             // text/plain
    355             case NOTE_ID:
    356                 return NOTE_STREAM_TYPES.filterMimeTypes(mimeTypeFilter);
    357 
    358                 // If the URI pattern doesn't match any permitted patterns, throws an exception.
    359             default:
    360                 throw new IllegalArgumentException("Unknown URI " + uri);
    361             }
    362     }
    363 
    364 
    365     /**
    366      * Returns a stream of data for each supported stream type. This method does a query on the
    367      * incoming URI, then uses
    368      * {@link android.content.ContentProvider#openPipeHelper(Uri, String, Bundle, Object,
    369      * PipeDataWriter)} to start another thread in which to convert the data into a stream.
    370      *
    371      * @param uri The URI pattern that points to the data stream
    372      * @param mimeTypeFilter A String containing a MIME type. This method tries to get a stream of
    373      * data with this MIME type.
    374      * @param opts Additional options supplied by the caller.  Can be interpreted as
    375      * desired by the content provider.
    376      * @return AssetFileDescriptor A handle to the file.
    377      * @throws FileNotFoundException if there is no file associated with the incoming URI.
    378      */
    379     @Override
    380     public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
    381             throws FileNotFoundException {
    382 
    383         // Checks to see if the MIME type filter matches a supported MIME type.
    384         String[] mimeTypes = getStreamTypes(uri, mimeTypeFilter);
    385 
    386         // If the MIME type is supported
    387         if (mimeTypes != null) {
    388 
    389             // Retrieves the note for this URI. Uses the query method defined for this provider,
    390             // rather than using the database query method.
    391             Cursor c = query(
    392                     uri,                    // The URI of a note
    393                     READ_NOTE_PROJECTION,   // Gets a projection containing the note's ID, title,
    394                                             // and contents
    395                     null,                   // No WHERE clause, get all matching records
    396                     null,                   // Since there is no WHERE clause, no selection criteria
    397                     null                    // Use the default sort order (modification date,
    398                                             // descending
    399             );
    400 
    401 
    402             // If the query fails or the cursor is empty, stop
    403             if (c == null || !c.moveToFirst()) {
    404 
    405                 // If the cursor is empty, simply close the cursor and return
    406                 if (c != null) {
    407                     c.close();
    408                 }
    409 
    410                 // If the cursor is null, throw an exception
    411                 throw new FileNotFoundException("Unable to query " + uri);
    412             }
    413 
    414             // Start a new thread that pipes the stream data back to the caller.
    415             return new AssetFileDescriptor(
    416                     openPipeHelper(uri, mimeTypes[0], opts, c, this), 0,
    417                     AssetFileDescriptor.UNKNOWN_LENGTH);
    418         }
    419 
    420         // If the MIME type is not supported, return a read-only handle to the file.
    421         return super.openTypedAssetFile(uri, mimeTypeFilter, opts);
    422     }
    423 
    424     /**
    425      * Implementation of {@link android.content.ContentProvider.PipeDataWriter}
    426      * to perform the actual work of converting the data in one of cursors to a
    427      * stream of data for the client to read.
    428      */
    429     @Override
    430     public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType,
    431             Bundle opts, Cursor c) {
    432         // We currently only support conversion-to-text from a single note entry,
    433         // so no need for cursor data type checking here.
    434         FileOutputStream fout = new FileOutputStream(output.getFileDescriptor());
    435         PrintWriter pw = null;
    436         try {
    437             pw = new PrintWriter(new OutputStreamWriter(fout, "UTF-8"));
    438             pw.println(c.getString(READ_NOTE_TITLE_INDEX));
    439             pw.println("");
    440             pw.println(c.getString(READ_NOTE_NOTE_INDEX));
    441         } catch (UnsupportedEncodingException e) {
    442             Log.w(TAG, "Ooops", e);
    443         } finally {
    444             c.close();
    445             if (pw != null) {
    446                 pw.flush();
    447             }
    448             try {
    449                 fout.close();
    450             } catch (IOException e) {
    451             }
    452         }
    453     }
    454 //END_INCLUDE(stream)
    455 
    456     /**
    457      * This is called when a client calls
    458      * {@link android.content.ContentResolver#insert(Uri, ContentValues)}.
    459      * Inserts a new row into the database. This method sets up default values for any
    460      * columns that are not included in the incoming map.
    461      * If rows were inserted, then listeners are notified of the change.
    462      * @return The row ID of the inserted row.
    463      * @throws SQLException if the insertion fails.
    464      */
    465     @Override
    466     public Uri insert(Uri uri, ContentValues initialValues) {
    467 
    468         // Validates the incoming URI. Only the full provider URI is allowed for inserts.
    469         if (sUriMatcher.match(uri) != NOTES) {
    470             throw new IllegalArgumentException("Unknown URI " + uri);
    471         }
    472 
    473         // A map to hold the new record's values.
    474         ContentValues values;
    475 
    476         // If the incoming values map is not null, uses it for the new values.
    477         if (initialValues != null) {
    478             values = new ContentValues(initialValues);
    479 
    480         } else {
    481             // Otherwise, create a new value map
    482             values = new ContentValues();
    483         }
    484 
    485         // Gets the current system time in milliseconds
    486         Long now = Long.valueOf(System.currentTimeMillis());
    487 
    488         // If the values map doesn't contain the creation date, sets the value to the current time.
    489         if (values.containsKey(NotePad.Notes.COLUMN_NAME_CREATE_DATE) == false) {
    490             values.put(NotePad.Notes.COLUMN_NAME_CREATE_DATE, now);
    491         }
    492 
    493         // If the values map doesn't contain the modification date, sets the value to the current
    494         // time.
    495         if (values.containsKey(NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE) == false) {
    496             values.put(NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE, now);
    497         }
    498 
    499         // If the values map doesn't contain a title, sets the value to the default title.
    500         if (values.containsKey(NotePad.Notes.COLUMN_NAME_TITLE) == false) {
    501             Resources r = Resources.getSystem();
    502             values.put(NotePad.Notes.COLUMN_NAME_TITLE, r.getString(android.R.string.untitled));
    503         }
    504 
    505         // If the values map doesn't contain note text, sets the value to an empty string.
    506         if (values.containsKey(NotePad.Notes.COLUMN_NAME_NOTE) == false) {
    507             values.put(NotePad.Notes.COLUMN_NAME_NOTE, "");
    508         }
    509 
    510         // Opens the database object in "write" mode.
    511         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    512 
    513         // Performs the insert and returns the ID of the new note.
    514         long rowId = db.insert(
    515             NotePad.Notes.TABLE_NAME,        // The table to insert into.
    516             NotePad.Notes.COLUMN_NAME_NOTE,  // A hack, SQLite sets this column value to null
    517                                              // if values is empty.
    518             values                           // A map of column names, and the values to insert
    519                                              // into the columns.
    520         );
    521 
    522         // If the insert succeeded, the row ID exists.
    523         if (rowId > 0) {
    524             // Creates a URI with the note ID pattern and the new row ID appended to it.
    525             Uri noteUri = ContentUris.withAppendedId(NotePad.Notes.CONTENT_ID_URI_BASE, rowId);
    526 
    527             // Notifies observers registered against this provider that the data changed.
    528             getContext().getContentResolver().notifyChange(noteUri, null);
    529             return noteUri;
    530         }
    531 
    532         // If the insert didn't succeed, then the rowID is <= 0. Throws an exception.
    533         throw new SQLException("Failed to insert row into " + uri);
    534     }
    535 
    536     /**
    537      * This is called when a client calls
    538      * {@link android.content.ContentResolver#delete(Uri, String, String[])}.
    539      * Deletes records from the database. If the incoming URI matches the note ID URI pattern,
    540      * this method deletes the one record specified by the ID in the URI. Otherwise, it deletes a
    541      * a set of records. The record or records must also match the input selection criteria
    542      * specified by where and whereArgs.
    543      *
    544      * If rows were deleted, then listeners are notified of the change.
    545      * @return If a "where" clause is used, the number of rows affected is returned, otherwise
    546      * 0 is returned. To delete all rows and get a row count, use "1" as the where clause.
    547      * @throws IllegalArgumentException if the incoming URI pattern is invalid.
    548      */
    549     @Override
    550     public int delete(Uri uri, String where, String[] whereArgs) {
    551 
    552         // Opens the database object in "write" mode.
    553         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    554         String finalWhere;
    555 
    556         int count;
    557 
    558         // Does the delete based on the incoming URI pattern.
    559         switch (sUriMatcher.match(uri)) {
    560 
    561             // If the incoming pattern matches the general pattern for notes, does a delete
    562             // based on the incoming "where" columns and arguments.
    563             case NOTES:
    564                 count = db.delete(
    565                     NotePad.Notes.TABLE_NAME,  // The database table name
    566                     where,                     // The incoming where clause column names
    567                     whereArgs                  // The incoming where clause values
    568                 );
    569                 break;
    570 
    571                 // If the incoming URI matches a single note ID, does the delete based on the
    572                 // incoming data, but modifies the where clause to restrict it to the
    573                 // particular note ID.
    574             case NOTE_ID:
    575                 /*
    576                  * Starts a final WHERE clause by restricting it to the
    577                  * desired note ID.
    578                  */
    579                 finalWhere =
    580                         NotePad.Notes._ID +                              // The ID column name
    581                         " = " +                                          // test for equality
    582                         uri.getPathSegments().                           // the incoming note ID
    583                             get(NotePad.Notes.NOTE_ID_PATH_POSITION)
    584                 ;
    585 
    586                 // If there were additional selection criteria, append them to the final
    587                 // WHERE clause
    588                 if (where != null) {
    589                     finalWhere = finalWhere + " AND " + where;
    590                 }
    591 
    592                 // Performs the delete.
    593                 count = db.delete(
    594                     NotePad.Notes.TABLE_NAME,  // The database table name.
    595                     finalWhere,                // The final WHERE clause
    596                     whereArgs                  // The incoming where clause values.
    597                 );
    598                 break;
    599 
    600             // If the incoming pattern is invalid, throws an exception.
    601             default:
    602                 throw new IllegalArgumentException("Unknown URI " + uri);
    603         }
    604 
    605         /* Gets a handle to the content resolver object for the current context, and notifies it
    606          * that the incoming URI changed. The object passes this along to the resolver framework,
    607          * and observers that have registered themselves for the provider are notified.
    608          */
    609         getContext().getContentResolver().notifyChange(uri, null);
    610 
    611         // Returns the number of rows deleted.
    612         return count;
    613     }
    614 
    615     /**
    616      * This is called when a client calls
    617      * {@link android.content.ContentResolver#update(Uri,ContentValues,String,String[])}
    618      * Updates records in the database. The column names specified by the keys in the values map
    619      * are updated with new data specified by the values in the map. If the incoming URI matches the
    620      * note ID URI pattern, then the method updates the one record specified by the ID in the URI;
    621      * otherwise, it updates a set of records. The record or records must match the input
    622      * selection criteria specified by where and whereArgs.
    623      * If rows were updated, then listeners are notified of the change.
    624      *
    625      * @param uri The URI pattern to match and update.
    626      * @param values A map of column names (keys) and new values (values).
    627      * @param where An SQL "WHERE" clause that selects records based on their column values. If this
    628      * is null, then all records that match the URI pattern are selected.
    629      * @param whereArgs An array of selection criteria. If the "where" param contains value
    630      * placeholders ("?"), then each placeholder is replaced by the corresponding element in the
    631      * array.
    632      * @return The number of rows updated.
    633      * @throws IllegalArgumentException if the incoming URI pattern is invalid.
    634      */
    635     @Override
    636     public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
    637 
    638         // Opens the database object in "write" mode.
    639         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    640         int count;
    641         String finalWhere;
    642 
    643         // Does the update based on the incoming URI pattern
    644         switch (sUriMatcher.match(uri)) {
    645 
    646             // If the incoming URI matches the general notes pattern, does the update based on
    647             // the incoming data.
    648             case NOTES:
    649 
    650                 // Does the update and returns the number of rows updated.
    651                 count = db.update(
    652                     NotePad.Notes.TABLE_NAME, // The database table name.
    653                     values,                   // A map of column names and new values to use.
    654                     where,                    // The where clause column names.
    655                     whereArgs                 // The where clause column values to select on.
    656                 );
    657                 break;
    658 
    659             // If the incoming URI matches a single note ID, does the update based on the incoming
    660             // data, but modifies the where clause to restrict it to the particular note ID.
    661             case NOTE_ID:
    662                 // From the incoming URI, get the note ID
    663                 String noteId = uri.getPathSegments().get(NotePad.Notes.NOTE_ID_PATH_POSITION);
    664 
    665                 /*
    666                  * Starts creating the final WHERE clause by restricting it to the incoming
    667                  * note ID.
    668                  */
    669                 finalWhere =
    670                         NotePad.Notes._ID +                              // The ID column name
    671                         " = " +                                          // test for equality
    672                         uri.getPathSegments().                           // the incoming note ID
    673                             get(NotePad.Notes.NOTE_ID_PATH_POSITION)
    674                 ;
    675 
    676                 // If there were additional selection criteria, append them to the final WHERE
    677                 // clause
    678                 if (where !=null) {
    679                     finalWhere = finalWhere + " AND " + where;
    680                 }
    681 
    682 
    683                 // Does the update and returns the number of rows updated.
    684                 count = db.update(
    685                     NotePad.Notes.TABLE_NAME, // The database table name.
    686                     values,                   // A map of column names and new values to use.
    687                     finalWhere,               // The final WHERE clause to use
    688                                               // placeholders for whereArgs
    689                     whereArgs                 // The where clause column values to select on, or
    690                                               // null if the values are in the where argument.
    691                 );
    692                 break;
    693             // If the incoming pattern is invalid, throws an exception.
    694             default:
    695                 throw new IllegalArgumentException("Unknown URI " + uri);
    696         }
    697 
    698         /* Gets a handle to the content resolver object for the current context, and notifies it
    699          * that the incoming URI changed. The object passes this along to the resolver framework,
    700          * and observers that have registered themselves for the provider are notified.
    701          */
    702         getContext().getContentResolver().notifyChange(uri, null);
    703 
    704         // Returns the number of rows updated.
    705         return count;
    706     }
    707 
    708     /**
    709      * A test package can call this to get a handle to the database underlying NotePadProvider,
    710      * so it can insert test data into the database. The test case class is responsible for
    711      * instantiating the provider in a test context; {@link android.test.ProviderTestCase2} does
    712      * this during the call to setUp()
    713      *
    714      * @return a handle to the database helper object for the provider's data.
    715      */
    716     DatabaseHelper getOpenHelperForTest() {
    717         return mOpenHelper;
    718     }
    719 }
    720