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