• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
sUriMatcher.addURI(NotePad.AUTHORITY, "notes", NOTES)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
sUriMatcher.addURI(NotePad.AUTHORITY, "notes/#", NOTE_ID)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
sUriMatcher.addURI(NotePad.AUTHORITY, "live_folders/notes", LIVE_FOLDER_NOTES)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"
sNotesProjectionMap.put(NotePad.Notes._ID, NotePad.Notes._ID)142         sNotesProjectionMap.put(NotePad.Notes._ID, NotePad.Notes._ID);
143 
144         // Maps "title" to "title"
sNotesProjectionMap.put(NotePad.Notes.COLUMN_NAME_TITLE, NotePad.Notes.COLUMN_NAME_TITLE)145         sNotesProjectionMap.put(NotePad.Notes.COLUMN_NAME_TITLE, NotePad.Notes.COLUMN_NAME_TITLE);
146 
147         // Maps "note" to "note"
sNotesProjectionMap.put(NotePad.Notes.COLUMN_NAME_NOTE, NotePad.Notes.COLUMN_NAME_NOTE)148         sNotesProjectionMap.put(NotePad.Notes.COLUMN_NAME_NOTE, NotePad.Notes.COLUMN_NAME_NOTE);
149 
150         // Maps "created" to "created"
sNotesProjectionMap.put(NotePad.Notes.COLUMN_NAME_CREATE_DATE, NotePad.Notes.COLUMN_NAME_CREATE_DATE)151         sNotesProjectionMap.put(NotePad.Notes.COLUMN_NAME_CREATE_DATE,
152                 NotePad.Notes.COLUMN_NAME_CREATE_DATE);
153 
154         // Maps "modified" to "modified"
sNotesProjectionMap.put( NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE, NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE)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
sLiveFolderProjectionMap.put(LiveFolders._ID, NotePad.Notes._ID + " AS " + LiveFolders._ID)167         sLiveFolderProjectionMap.put(LiveFolders._ID, NotePad.Notes._ID + " AS " + LiveFolders._ID);
168 
169         // Maps "NAME" to "title AS NAME"
sLiveFolderProjectionMap.put(LiveFolders.NAME, NotePad.Notes.COLUMN_NAME_TITLE + " AS " + LiveFolders.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 
DatabaseHelper(Context context)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
onCreate(SQLiteDatabase db)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
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)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
onCreate()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
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)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
getType(Uri uri)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
getStreamTypes(Uri uri, String mimeTypeFilter)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
openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)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
writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType, Bundle opts, Cursor c)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
insert(Uri uri, ContentValues initialValues)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
delete(Uri uri, String where, String[] whereArgs)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
update(Uri uri, ContentValues values, String where, String[] whereArgs)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      */
getOpenHelperForTest()749     DatabaseHelper getOpenHelperForTest() {
750         return mOpenHelper;
751     }
752 }
753