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