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 sUriMatcher.addURI(NotePad.AUTHORITY, "notes", NOTES)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 sUriMatcher.addURI(NotePad.AUTHORITY, "notes/#", NOTE_ID)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" sNotesProjectionMap.put(NotePad.Notes._ID, NotePad.Notes._ID)130 sNotesProjectionMap.put(NotePad.Notes._ID, NotePad.Notes._ID); 131 132 // Maps "title" to "title" sNotesProjectionMap.put(NotePad.Notes.COLUMN_NAME_TITLE, NotePad.Notes.COLUMN_NAME_TITLE)133 sNotesProjectionMap.put(NotePad.Notes.COLUMN_NAME_TITLE, NotePad.Notes.COLUMN_NAME_TITLE); 134 135 // Maps "note" to "note" sNotesProjectionMap.put(NotePad.Notes.COLUMN_NAME_NOTE, NotePad.Notes.COLUMN_NAME_NOTE)136 sNotesProjectionMap.put(NotePad.Notes.COLUMN_NAME_NOTE, NotePad.Notes.COLUMN_NAME_NOTE); 137 138 // Maps "created" to "created" sNotesProjectionMap.put(NotePad.Notes.COLUMN_NAME_CREATE_DATE, NotePad.Notes.COLUMN_NAME_CREATE_DATE)139 sNotesProjectionMap.put(NotePad.Notes.COLUMN_NAME_CREATE_DATE, 140 NotePad.Notes.COLUMN_NAME_CREATE_DATE); 141 142 // Maps "modified" to "modified" sNotesProjectionMap.put( NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE, NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE)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 DatabaseHelper(Context context)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 onCreate(SQLiteDatabase db)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 onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)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 onCreate()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 query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)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 getType(Uri uri)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 getStreamTypes(Uri uri, String mimeTypeFilter)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 openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)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 writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType, Bundle opts, Cursor c)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 insert(Uri uri, ContentValues initialValues)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 delete(Uri uri, String where, String[] whereArgs)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 update(Uri uri, ContentValues values, String where, String[] whereArgs)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 */ getOpenHelperForTest()716 DatabaseHelper getOpenHelperForTest() { 717 return mOpenHelper; 718 } 719 } 720