package com.ianhanniballake.contractiontimer.provider;
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.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.provider.BaseColumns;
import android.support.annotation.NonNull;
import android.support.v4.database.DatabaseUtilsCompat;
import android.text.TextUtils;
import android.util.Log;
import com.ianhanniballake.contractiontimer.BuildConfig;
import java.util.HashMap;
/**
* Provides access to a database of contractions.
*/
public class ContractionProvider extends ContentProvider {
/**
* The incoming URI matches the Contraction ID URI pattern
*/
private static final int CONTRACTION_ID = 2;
/**
* The incoming URI matches the Contractions URI pattern
*/
private static final int CONTRACTIONS = 1;
/**
* The database that the provider uses as its underlying data store
*/
private static final String DATABASE_NAME = "contractions.db";
/**
* The database version
*/
private static final int DATABASE_VERSION = 2;
/**
* Used for debugging and logging
*/
private static final String TAG = "ContractionProvider";
/**
* A UriMatcher instance
*/
private static final UriMatcher uriMatcher = ContractionProvider.buildUriMatcher();
/**
* An identity all column projection mapping
*/
final HashMap<String, String> allColumnProjectionMap = ContractionProvider.buildAllColumnProjectionMap();
/**
* Handle to a new DatabaseHelper.
*/
private DatabaseHelper databaseHelper;
/**
* Creates and initializes a column project for all columns
*
* @return The all column projection map
*/
private static HashMap<String, String> buildAllColumnProjectionMap() {
final HashMap<String, String> allColumnProjectionMap = new HashMap<>();
allColumnProjectionMap.put(BaseColumns._ID, BaseColumns._ID);
allColumnProjectionMap.put(ContractionContract.Contractions.COLUMN_NAME_START_TIME,
ContractionContract.Contractions.COLUMN_NAME_START_TIME);
allColumnProjectionMap.put(ContractionContract.Contractions.COLUMN_NAME_END_TIME,
ContractionContract.Contractions.COLUMN_NAME_END_TIME);
allColumnProjectionMap.put(ContractionContract.Contractions.COLUMN_NAME_NOTE,
ContractionContract.Contractions.COLUMN_NAME_NOTE);
return allColumnProjectionMap;
}
/**
* Creates and initializes the URI matcher
*
* @return the URI Matcher
*/
private static UriMatcher buildUriMatcher() {
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI(ContractionContract.AUTHORITY, ContractionContract.Contractions.TABLE_NAME,
ContractionProvider.CONTRACTIONS);
matcher.addURI(ContractionContract.AUTHORITY, ContractionContract.Contractions.TABLE_NAME + "/#",
ContractionProvider.CONTRACTION_ID);
return matcher;
}
@Override
public int delete(@NonNull final Uri uri, final String where, final String[] whereArgs) {
// Opens the database object in "write" mode.
final SQLiteDatabase db = databaseHelper.getWritableDatabase();
int count;
// Does the delete based on the incoming URI pattern.
switch (ContractionProvider.uriMatcher.match(uri)) {
case CONTRACTIONS:
// If the incoming pattern matches the general pattern for
// contractions, does a delete based on the incoming "where"
// column and arguments.
count = db.delete(ContractionContract.Contractions.TABLE_NAME, where, whereArgs);
break;
case CONTRACTION_ID:
// If the incoming URI matches a single contraction ID, does the
// delete based on the incoming data, but modifies the where
// clause to restrict it to the particular contraction ID.
final String finalWhere = DatabaseUtilsCompat.concatenateWhere(where, BaseColumns._ID + "=?");
final String[] finalWhereArgs = DatabaseUtilsCompat.appendSelectionArgs(whereArgs,
new String[]{Long.toString(ContentUris.parseId(uri))});
count = db.delete(ContractionContract.Contractions.TABLE_NAME, finalWhere, finalWhereArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
@Override
public String getType(@NonNull final Uri uri) {
/**
* Chooses the MIME type based on the incoming URI pattern
*/
switch (ContractionProvider.uriMatcher.match(uri)) {
case CONTRACTIONS:
// If the pattern is for contractions, returns the general
// content type.
return ContractionContract.Contractions.CONTENT_TYPE;
case CONTRACTION_ID:
// If the pattern is for contraction IDs, returns the
// contraction ID content type.
return ContractionContract.Contractions.CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
}
@Override
public Uri insert(@NonNull final Uri uri, final ContentValues initialValues) {
// Validates the incoming URI. Only the full provider URI is allowed for
// inserts.
if (ContractionProvider.uriMatcher.match(uri) != ContractionProvider.CONTRACTIONS)
throw new IllegalArgumentException("Unknown URI " + uri);
ContentValues values;
if (initialValues != null)
values = new ContentValues(initialValues);
else
values = new ContentValues();
if (!values.containsKey(ContractionContract.Contractions.COLUMN_NAME_START_TIME))
values.put(ContractionContract.Contractions.COLUMN_NAME_START_TIME, System.currentTimeMillis());
if (!values.containsKey(ContractionContract.Contractions.COLUMN_NAME_NOTE))
values.put(ContractionContract.Contractions.COLUMN_NAME_NOTE, "");
final SQLiteDatabase db = databaseHelper.getWritableDatabase();
final long rowId = db.insert(ContractionContract.Contractions.TABLE_NAME,
ContractionContract.Contractions.COLUMN_NAME_START_TIME, values);
// If the insert succeeded, the row ID exists.
if (rowId > 0) {
// Creates a URI with the contraction ID pattern and the new row ID
// appended to it.
final Uri contractionUri = ContentUris.withAppendedId(ContractionContract.Contractions.CONTENT_ID_URI_BASE,
rowId);
getContext().getContentResolver().notifyChange(contractionUri, null);
return contractionUri;
}
// If the insert didn't succeed, then the rowID is <= 0
throw new SQLException("Failed to insert row into " + uri);
}
/**
* Creates the underlying DatabaseHelper
*
* @see android.content.ContentProvider#onCreate()
*/
@Override
public boolean onCreate() {
databaseHelper = new DatabaseHelper(getContext());
return true;
}
@Override
public Cursor query(@NonNull final Uri uri, final String[] projection, final String selection,
final String[] selectionArgs, final String sortOrder) {
// Constructs a new query builder and sets its table name
final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(ContractionContract.Contractions.TABLE_NAME);
qb.setProjectionMap(allColumnProjectionMap);
String finalSortOrder = sortOrder;
if (TextUtils.isEmpty(sortOrder))
finalSortOrder = ContractionContract.Contractions.DEFAULT_SORT_ORDER;
switch (ContractionProvider.uriMatcher.match(uri)) {
case CONTRACTIONS:
break;
case CONTRACTION_ID:
// If the incoming URI is for a single contraction identified by
// its ID, appends "_ID = <contractionID>" to the where clause,
// so that it selects that single contraction
qb.appendWhere(BaseColumns._ID + "=" + uri.getLastPathSegment());
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
final SQLiteDatabase db = databaseHelper.getReadableDatabase();
final Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, finalSortOrder, null);
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
@Override
public int update(@NonNull final Uri uri, final ContentValues values, final String selection,
final String[] selectionArgs) {
final SQLiteDatabase db = databaseHelper.getWritableDatabase();
int count;
switch (ContractionProvider.uriMatcher.match(uri)) {
case CONTRACTIONS:
// If the incoming URI matches the general contractions pattern,
// does the update based on the incoming data.
count = db.update(ContractionContract.Contractions.TABLE_NAME, values, selection, selectionArgs);
break;
case CONTRACTION_ID:
// If the incoming URI matches a single contraction ID, does the
// update based on the incoming data, but modifies the where
// clause to restrict it to the particular contraction ID.
final String finalWhere = DatabaseUtilsCompat.concatenateWhere(selection, BaseColumns._ID + "=?");
final String[] finalWhereArgs = DatabaseUtilsCompat.appendSelectionArgs(selectionArgs,
new String[]{Long.toString(ContentUris.parseId(uri))});
count = db.update(ContractionContract.Contractions.TABLE_NAME, values, finalWhere, finalWhereArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
/**
* This class helps open, create, and upgrade the database file.
*/
static class DatabaseHelper extends SQLiteOpenHelper {
/**
* Creates a new DatabaseHelper
*
* @param context context of this database
*/
DatabaseHelper(final Context context) {
super(context, ContractionProvider.DATABASE_NAME, null, ContractionProvider.DATABASE_VERSION);
}
/**
* Creates the underlying database with table name and column names taken from the ContractionContract class.
*/
@Override
public void onCreate(final SQLiteDatabase db) {
if (BuildConfig.DEBUG)
Log.d(ContractionProvider.TAG, "Creating the " + ContractionContract.Contractions.TABLE_NAME + " table");
db.execSQL("CREATE TABLE " + ContractionContract.Contractions.TABLE_NAME + " (" + BaseColumns._ID
+ " INTEGER PRIMARY KEY AUTOINCREMENT," + ContractionContract.Contractions.COLUMN_NAME_START_TIME
+ " INTEGER," + ContractionContract.Contractions.COLUMN_NAME_END_TIME + " INTEGER,"
+ ContractionContract.Contractions.COLUMN_NAME_NOTE + " TEXT);");
}
/**
* Demonstrates that the provider must consider what happens when the underlying database is changed. Note that
* this currently just destroys and recreates the database - should upgrade in place
*/
@Override
public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
if (BuildConfig.DEBUG)
Log.w(ContractionProvider.TAG, "Upgrading database from version " + oldVersion + " to " + newVersion
+ ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS " + ContractionContract.Contractions.TABLE_NAME);
onCreate(db);
}
}
}