/**
* FishDiaryProvider.java
*
* Ver 1.0, 2012-12-22, alex_yh, Create file.
*/
package com.flounder.fishDiary.data;
import java.util.HashMap;
import com.flounder.fishDiary.FishPreferences;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.text.TextUtils;
public class FishDiaryProvider extends ContentProvider {
private static final String DB_NAME = "fish_diary.db";
private static final int DB_VERSION = 1;
// Constants used by the URI matcher to choose an action
// based on the pattern of the incoming URI
/** The incoming URI matches the Notes URI pattern */
private static final int NOTES = 1;
/** The incoming URI matches the Note ID URI pattern */
private static final int NOTE_ID = 2;
/** A projection map used to select columns from the database */
private static HashMap<String, String> sNotesProjectionMap;
private static final UriMatcher sUriMatcher;
static {
// Creates and initializes the URI matcher
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
/** Add a pattern that routes URIs terminated with "notes" to a NOTES operation */
sUriMatcher.addURI(FishDiary.AUTHORITY, "notes", NOTES);
/**
* Add a pattern that routes URIs terminated with "notes" plus an integer to a
* note ID operation
*/
sUriMatcher.addURI(FishDiary.AUTHORITY, "notes/#", NOTE_ID);
// Creates and initialized a projection map that returns all columns
/**
* Creates a new projection map instance.
* The map returns a column name given a string. The two are usually equal
*/
sNotesProjectionMap = new HashMap<String, String>();
/** Maps the string "_ID" to the column name "_ID" */
sNotesProjectionMap.put(FishDiary.Notes._ID, FishDiary.Notes._ID);
/** Maps "title" to "title" */
sNotesProjectionMap.put(FishDiary.Notes.COLUMN_NAME_TITLE,
FishDiary.Notes.COLUMN_NAME_TITLE);
/** Maps "note" to "note" */
sNotesProjectionMap.put(FishDiary.Notes.COLUMN_NAME_NOTE,
FishDiary.Notes.COLUMN_NAME_NOTE);
/** Maps "tag" to "tag" */
sNotesProjectionMap.put(FishDiary.Notes.COLUMN_NAME_TAG,
FishDiary.Notes.COLUMN_NAME_TAG);
/** Maps "author" to "author" */
sNotesProjectionMap.put(FishDiary.Notes.COLUMN_NAME_AUTHOR,
FishDiary.Notes.COLUMN_NAME_AUTHOR);
/** Maps "encrypted" to "encrypted" */
sNotesProjectionMap.put(FishDiary.Notes.COLUMN_NAME_ENCRYTED,
FishDiary.Notes.COLUMN_NAME_ENCRYTED);
/** Maps "created" to "created" */
sNotesProjectionMap.put(FishDiary.Notes.COLUMN_NAME_CREATE_DATE,
FishDiary.Notes.COLUMN_NAME_CREATE_DATE);
/** Maps "modified" to "modified" */
sNotesProjectionMap.put(FishDiary.Notes.COLUMN_NAME_MODIFICATION_DATE,
FishDiary.Notes.COLUMN_NAME_MODIFICATION_DATE);
}
private static final String CREATE_TABLE = "CREATE TABLE "
+ FishDiary.Notes.TABLE_NAME + " ("
+ FishDiary.Notes._ID + " INTEGER PRIMARY KEY,"
+ FishDiary.Notes.COLUMN_NAME_TITLE + " TEXT,"
+ FishDiary.Notes.COLUMN_NAME_NOTE + " TEXT,"
+ FishDiary.Notes.COLUMN_NAME_TAG + " TEXT,"
+ FishDiary.Notes.COLUMN_NAME_AUTHOR + " TEXT,"
+ FishDiary.Notes.COLUMN_NAME_ENCRYTED + " INTEGER,"
+ FishDiary.Notes.COLUMN_NAME_CREATE_DATE + " INTEGER,"
+ FishDiary.Notes.COLUMN_NAME_MODIFICATION_DATE + " INTEGER" + ");";
private DatebaseHelper mOpenHelper;
/** This class helps open, create, and upgrade the database file. */
public static class DatebaseHelper extends SQLiteOpenHelper {
public DatebaseHelper(Context context) {
// calls the super constructor, requesting the default cursor factory
super(context, DB_NAME, null, DB_VERSION);
}
/**
* Creates the underlying database with table name and column names taken from the
* FishDiary class
*/
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS notes");
onCreate(db);
}
}
/** Initialized the provider by creating a new DatabaseHelper */
@Override
public boolean onCreate() {
// creates a new helper object. Note that the database itself isn't opened until
// something tries to access it, and it's only created if it doesn't already exist
mOpenHelper = new DatebaseHelper(getContext());
// assumes that any failures will be reported by a thrown exception
return true;
}
/**
* Deletes records from the database.
* This is called when a client calls ContenteResolver.delete(Uri, String, String[]).
* If the incoming URI matches the note ID URI pattern,
* this method deletes the one record specified by the ID in the URI.
* Otherwise, it deletes a set of records.
* The record or records must also match the input selection criteria specified by
* where and whereArgs.
*
* If rows were deleted, then listeners are notified of the change.
*
* @return If a "where" clause is used, the number of rows affected is returned.
* Otherwise 0 is returned.
* To delete all rows and get a row count, use "1" as the where clause.
*
* @throws IllegalArgumentException
* if the incoming URI pattern is invalid.
*/
@Override
public int delete(Uri uri, String where, String[] whereArgs) {
// opens the database object in "write" mode
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
String finalWhere;
int count;
// does the delete based on the incoming URI pattern.
switch (sUriMatcher.match(uri)) {
// If the incoming pattern matches the general pattern for notes,
// does a delete based on the incoming "where" columns and arguments
case NOTES:
count = db.delete(
FishDiary.Notes.TABLE_NAME, // The database table name
where, // The incoming where clause column names
whereArgs // The incoming where clause values
);
break;
// If the incoming URI matches a single note ID
// does the delete based on the incoming data,
// but modifies the where clause to restrict it to the particular note ID
case NOTE_ID:
// starts a final WHERE clause by restricting it to the desired note ID
finalWhere = FishDiary.Notes._ID
+ " = "
+ uri.getPathSegments().get(
FishDiary.Notes.NOTE_ID_PATH_POSITION);
// if there were additional selection criteria, append them to the final WHERE
// clause
if (where != null) {
finalWhere = finalWhere + " AND " + where;
}
// performs the delete
count = db.delete(FishDiary.Notes.TABLE_NAME, finalWhere, whereArgs);
break;
// If the incoming pattern is invalid, throws an exception
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// Gets a handle to the content resolver object for the current context
// and notifies it that the incoming URI changed.
// The object passed this along to the resolver framework,
// and observers that have registered themselves for the provider are notified
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
/**
* This is called when a client calls ContentResolver.getType(Uri).
* Returns the MIME data type of the URI given as a parameter
*
* @param uri
* The URI whose MIME type is desired.
* @return The MIME type of the URI
* @throws IllegalArgumentException
* if the incoming URI pattern is invalid
*/
@Override
public String getType(Uri uri) {
switch (sUriMatcher.match(uri)) {
// if the pattern is for notes, returns the general content type
case NOTES:
return FishDiary.Notes.CONTENT_TYPE;
// if the pattern is for note IDs, returns the note ID content type
case NOTE_ID:
return FishDiary.Notes.CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
}
/**
* This is called when a client calls ContentResolver.insert(Uri, ContentValues).
* Inserts a new row into the database. This method sets up default values for any
* columns that are not included in the incoming map.
* If rows were inserted, then listeners are notified of the change.
*
* @return The row ID of the inserted row.
* @throws SQLException
* if the insertion fails
*/
@Override
public Uri insert(Uri uri, ContentValues initialValues) {
// validates the incoming URI. Only the full provider URI is allowd for inserts
if (sUriMatcher.match(uri) != NOTES) {
throw new IllegalArgumentException("Unknown URI " + uri);
}
// a map to hold the new record's values
ContentValues values;
if (initialValues != null) {
values = new ContentValues(initialValues);
} else {
values = new ContentValues();
}
// if the values map doesn't contain the creation/modification data
// sets the value to the current time
Long now = Long.valueOf(System.currentTimeMillis());
if (values.containsKey(FishDiary.Notes.COLUMN_NAME_CREATE_DATE) == false) {
values.put(FishDiary.Notes.COLUMN_NAME_CREATE_DATE, now);
}
if (values.containsKey(FishDiary.Notes.COLUMN_NAME_MODIFICATION_DATE) == false) {
values.put(FishDiary.Notes.COLUMN_NAME_MODIFICATION_DATE, now);
}
// if the values map doesn't contain a title, set the value to empty
if (values.containsKey(FishDiary.Notes.COLUMN_NAME_TITLE) == false) {
values.put(FishDiary.Notes.COLUMN_NAME_TITLE, "");
}
// if the values map doesn't contain a tag, set the value to empty
if (values.containsKey(FishDiary.Notes.COLUMN_NAME_TAG) == false) {
values.put(FishDiary.Notes.COLUMN_NAME_TAG, "");
}
// insert author name
if (values.containsKey(FishDiary.Notes.COLUMN_NAME_AUTHOR) == false) {
values.put(FishDiary.Notes.COLUMN_NAME_AUTHOR,
FishPreferences.getAuthorName(getContext()));
}
if (values.containsKey(FishDiary.Notes.COLUMN_NAME_ENCRYTED) == false) {
values.put(FishDiary.Notes.COLUMN_NAME_ENCRYTED, 0);
}
// if the values map dosen't contain note text, set the value to an empty string
if (values.containsKey(FishDiary.Notes.COLUMN_NAME_NOTE) == false) {
values.put(FishDiary.Notes.COLUMN_NAME_NOTE, "");
}
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
long rowId = db.insert(FishDiary.Notes.TABLE_NAME,
FishDiary.Notes.COLUMN_NAME_NOTE, values);
// if the insert succeeded, the rowId exists
if (rowId > 0) {
// create a URI with the note ID pattern and the new row ID appended to it
Uri noteUri = ContentUris.withAppendedId(
FishDiary.Notes.CONTENT_ID_URI_BASE, rowId);
// notifies observers registered against this provider that the data changed
getContext().getContentResolver().notifyChange(noteUri, null);
return noteUri;
}
// if the insert didn't succeed, the the rowId is <= 0. Throws an exception
throw new SQLException("Failed to insert row into " + uri);
}
/**
* This method is called when a client calls ContentResolver.query(Uri, String[],
* String, String[], String). Queries the database and returns a cursor containing the
* results.
*
* @return A cursor containing the results of the query. The cursor exists but is
* empty if the query returns no results or an exception occurs.
* @throws IllegalArgumentException
* if the incoming URI pattern is invalid.
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// Constructs a new query builder and sets its table name
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
builder.setTables(FishDiary.Notes.TABLE_NAME);
// Choose the projection and adjust the "where" clause based on URI
// pattern-matching
switch (sUriMatcher.match(uri)) {
// If the incoming URI is for notes, chooses the Notes projection
case NOTES:
builder.setProjectionMap(sNotesProjectionMap);
break;
// If the incoming URI is for a single note identified by its ID, chooses the note
// ID projection, and appends "_ID = <noteID>" to the where clause, so that it
// selects that single note
case NOTE_ID:
builder.setProjectionMap(sNotesProjectionMap);
builder.appendWhere(FishDiary.Notes._ID
+ "="
+ uri.getPathSegments().get(
FishDiary.Notes.NOTE_ID_PATH_POSITION));
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// If no sort order is specified, uses the default
String orderBy;
if (TextUtils.isEmpty(sortOrder)) {
orderBy = FishDiary.Notes.DEFAULT_SORT_ORDER;
} else {
orderBy = sortOrder;
}
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
// Performs the query, If no problems occur trying to read the database, then a
// Cursor object is returned; otherwise, the cursor variable contains null.
// If no records were selected, then the Cursor object is empty, and
// Cursor.getCount() returns 0.
Cursor c = builder.query(db, projection, selection,
selectionArgs, null, null,
orderBy);
// Tells the Cursor what URI to watch, so it knows when its source data changes
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
/**
* This is called when a client calls ContentResolver.update(Uri, ContentValues,
* String, String[])
* Updates records in the database. The column names specified by the keys in the
* values map are updated with new data specified by the values in the map.
* If the incoming URI matches the note ID URI pattern, then the method updates the
* one record specified by the ID in the URI;
* otherwise, it updates a set of records. The record or records must match the input
* selection criteria specified by where and whereArgs.
* If rows were updated, then listeners are notified of the change.
*
* @param uri
* The URI pattern to match and update.
* @param values
* A map of column names (keys) and new values (values).
* @param where
* An SQL "WHERE" clause that selects records based on their column values.
* If this is null, then records that match the URI pattern are selected.
* @param whereArgs
* An array of selection criteria. If the "where" param contains value
* placeholders("?"), then each placeholder is replaced by the
* corresponding element in the array.
* @return The number of rows updated.
* @throws IllegalArgumentException
* if the incoming URI pattern is invalid.
*/
@Override
public int update(Uri uri, ContentValues values, String where,
String[] whereArgs) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int count;
String finalWhere;
// Does the update based on the incoming URI pattern
switch (sUriMatcher.match(uri)) {
// if the incoming URI matches the general notes pattern,
// does the update based on the incoming data
case NOTES:
count = db.update(FishDiary.Notes.TABLE_NAME, values, where, whereArgs);
break;
// if the incoming URI matches a single note ID
// does the update based on the incoming data
// but modifies the where clause to restrict it to the particular note ID.
case NOTE_ID:
finalWhere = FishDiary.Notes._ID
+ " = "
+ uri.getPathSegments().get(
FishDiary.Notes.NOTE_ID_PATH_POSITION);
if (where != null) {
finalWhere = finalWhere + " AND " + where;
}
count = db.update(FishDiary.Notes.TABLE_NAME, values, finalWhere,
whereArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
}