package org.dodgybits.shuffle.android.persistence.provider; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import android.app.SearchManager; import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; import android.content.Intent; import android.content.UriMatcher; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.provider.BaseColumns; import android.text.TextUtils; import android.util.Log; public abstract class AbstractCollectionProvider extends ContentProvider { public static final String cDatabaseName = "shuffle.db"; static final int cDatabaseVersion = 16; public static final String cTag = "ShuffleProvider"; public static interface ShuffleTable extends BaseColumns { static final String CONTENT_TYPE_PATH = "vnd.dodgybits"; static final String CONTENT_TYPE_PRE_PREFIX = "vnd.android.cursor.dir/"; static final String CONTENT_ITEM_TYPE_PRE_PREFIX = "vnd.android.cursor.item/"; static final String CONTENT_TYPE_PREFIX = CONTENT_TYPE_PRE_PREFIX+CONTENT_TYPE_PATH; static final String CONTENT_ITEM_TYPE_PREFIX = CONTENT_ITEM_TYPE_PRE_PREFIX+CONTENT_TYPE_PATH; public static final String MODIFIED_DATE = "modified"; public static final String TRACKS_ID = "tracks_id"; public static final String DELETED = "deleted"; public static final String ACTIVE = "active"; } protected static final int SEARCH = 3; protected static final int COLLECTION_MATCH_ID = 1; protected static final int ELEMENT_MATCH_ID = 2; protected static Map<String, String> createSuggestionsMap(String idField, String column1Field, String column2Field) { HashMap<String, String> sSuggestionProjectionMap = new HashMap<String, String>(); sSuggestionProjectionMap.put(SearchManager.SUGGEST_COLUMN_TEXT_1, column1Field + " AS " + SearchManager.SUGGEST_COLUMN_TEXT_1); sSuggestionProjectionMap.put(SearchManager.SUGGEST_COLUMN_TEXT_2, column2Field + " AS " + SearchManager.SUGGEST_COLUMN_TEXT_2); sSuggestionProjectionMap.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, idField + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID); sSuggestionProjectionMap.put(idField, idField); return sSuggestionProjectionMap; } protected static Map<String, String> createTableMap(String tableName, String... fieldNames) { HashMap<String, String> fieldNameMap = new HashMap<String, String>(); for (String fieldName : fieldNames) { fieldNameMap.put(fieldName, tableName + "."+fieldName); } return fieldNameMap; } private final String authority; protected DatabaseHelper mOpenHelper; protected Map<String, String> suggestionProjectionMap; protected final Map<Integer, RestrictionBuilder> restrictionBuilders; protected final Map<Integer, GroupByBuilder> groupByBuilders; protected final Map<Integer, CollectionUpdater> collectionUpdaters; protected final Map<Integer, ElementInserter> elementInserters; protected final Map<Integer, ElementDeleter> elementDeleters; private final String tableName; private final String updateIntentAction; private final Map<String, String> elementsMap; protected final Map<Integer,String> mimeTypes; protected final Uri contentUri; private String defaultSortOrder = null; protected void setDefaultSortOrder(String defaultSortOrder) { this.defaultSortOrder = defaultSortOrder; } protected String getTableName() { return tableName; } protected Map<String,String> getElementsMap() { return elementsMap; } private void notifyOnChange(Uri uri) { getContext().getContentResolver().notifyChange(uri, null); getContext().sendBroadcast(new Intent(updateIntentAction)); } public static interface RestrictionBuilder { void addRestrictions(Uri uri, SQLiteQueryBuilder qb); } private class EntireCollectionRestrictionBuilder implements RestrictionBuilder { @Override public void addRestrictions(Uri uri, SQLiteQueryBuilder qb) { qb.setTables(getTableName()); qb.setProjectionMap(getElementsMap()); } } private class ElementByIdRestrictionBuilder implements RestrictionBuilder { @Override public void addRestrictions(Uri uri, SQLiteQueryBuilder qb) { qb.setTables(getTableName()); qb.appendWhere("_id=" + uri.getPathSegments().get(1)); } } private class SearchRestrictionBuilder implements RestrictionBuilder { private final String[] searchFields; public SearchRestrictionBuilder(String[] searchFields) { super(); this.searchFields = searchFields; } @Override public void addRestrictions(Uri uri, SQLiteQueryBuilder qb) { qb.setTables(getTableName()); String query = uri.getLastPathSegment(); if (!TextUtils.isEmpty(query)) { for (int i = 0; i < searchFields.length; i++) { String field = searchFields[i]; qb.appendWhere(field + " LIKE "); qb.appendWhereEscapeString('%' + query + '%'); if (i < searchFields.length - 1) qb.appendWhere(" OR "); } } qb.setProjectionMap(suggestionProjectionMap); } } protected class CustomElementFilterRestrictionBuilder implements RestrictionBuilder { private final String tables; private final String restrictions; private final String idField; public CustomElementFilterRestrictionBuilder(String tables, String restrictions, String idField) { super(); this.tables = tables; this.restrictions = restrictions; this.idField = idField; } @Override public void addRestrictions(Uri uri, SQLiteQueryBuilder qb) { Map<String, String> projectionMap = new HashMap<String, String>(); projectionMap.put("_id", idField); projectionMap.put("count", "count(*)"); qb.setProjectionMap(projectionMap); qb.setTables(tables); qb.appendWhere(restrictions); } } public static interface GroupByBuilder { String getGroupBy(Uri uri); } protected class StandardGroupByBuilder implements GroupByBuilder { private String mGroupBy; public StandardGroupByBuilder(String groupBy) { mGroupBy = groupBy; } @Override public String getGroupBy(Uri uri) { return mGroupBy; } } protected final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); public AbstractCollectionProvider(String authority, String collectionNamePlural, String tableName, String updateIntentAction, String primaryKey, String idField,Uri contentUri, String... fields) { this.authority = authority; this.contentUri = contentUri; registerCollectionUrls(collectionNamePlural); this.restrictionBuilders = new HashMap<Integer, RestrictionBuilder>(); this.restrictionBuilders.put(COLLECTION_MATCH_ID, new EntireCollectionRestrictionBuilder()); this.restrictionBuilders.put(ELEMENT_MATCH_ID, new ElementByIdRestrictionBuilder()); this.tableName = tableName; this.updateIntentAction = updateIntentAction; this.elementsMap = createTableMap(tableName, fields); this.mimeTypes = new HashMap<Integer, String>(); this.mimeTypes.put(COLLECTION_MATCH_ID, getContentType()); this.mimeTypes.put(ELEMENT_MATCH_ID, getContentItemType()); this.collectionUpdaters = new HashMap<Integer, CollectionUpdater>(); this.collectionUpdaters.put(COLLECTION_MATCH_ID, new EntireCollectionUpdater()); this.collectionUpdaters.put(ELEMENT_MATCH_ID, new SingleElementUpdater()); this.elementInserters = new HashMap<Integer, ElementInserter>(); this.elementInserters.put(COLLECTION_MATCH_ID, new ElementInserterImpl(primaryKey)); this.elementDeleters = new HashMap<Integer, ElementDeleter>(); this.elementDeleters.put(COLLECTION_MATCH_ID, new EntireCollectionDeleter()); this.elementDeleters.put(ELEMENT_MATCH_ID, new ElementDeleterImpl(idField)); this.groupByBuilders = new HashMap<Integer, GroupByBuilder>(); } @Override public String getType(Uri uri) { String mimeType = mimeTypes.get(match(uri)); if (mimeType == null) throw new IllegalArgumentException("Unknown Uri " + uri); return mimeType; } SQLiteQueryBuilder createQueryBuilder() { return new SQLiteQueryBuilder(); } @Override public int delete(Uri uri, String where, String[] whereArgs) { SQLiteDatabase db = getWriteableDatabase(); int count = doDelete(uri, where, whereArgs, db); notifyOnChange(uri); return count; } SQLiteDatabase getReadableDatabase() { return mOpenHelper.getReadableDatabase(); } protected String getSortOrder(Uri uri, String sort) { if (defaultSortOrder != null && TextUtils.isEmpty(sort)) { return defaultSortOrder; } return sort; } protected SQLiteDatabase getWriteableDatabase() { return mOpenHelper.getWritableDatabase(); } @Override public Uri insert(Uri url, ContentValues initialValues) { ContentValues values; if (initialValues != null) { values = new ContentValues(initialValues); } else { values = new ContentValues(); } SQLiteDatabase db = getWriteableDatabase(); return doInsert(url, values, db); } protected void makeSearchable(String idField, String descriptionField, String detailsField, String...searchFields) { uriMatcher.addURI(authority, SearchManager.SUGGEST_URI_PATH_QUERY, SEARCH); uriMatcher.addURI(authority, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH); suggestionProjectionMap = createSuggestionsMap(idField,descriptionField,detailsField); restrictionBuilders.put(SEARCH, new SearchRestrictionBuilder(searchFields)); } public int match(Uri uri) { return uriMatcher.match(uri); } @Override public boolean onCreate() { Log.i(cTag, "+onCreate"); mOpenHelper = new DatabaseHelper(getContext()); return true; } protected int doUpdate(Uri uri, ContentValues values, String where, String[] whereArgs, SQLiteDatabase db) { CollectionUpdater updater = collectionUpdaters.get(match(uri)); if (updater == null) throw new IllegalArgumentException("Unknown URL " + uri); return updater.update(uri, values, where, whereArgs, db); } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sort) { SQLiteQueryBuilder qb = createQueryBuilder(); SQLiteDatabase db = getReadableDatabase(); addRestrictions(uri, qb); String orderBy = getSortOrder(uri, sort); String groupBy = getGroupBy(uri); if (Log.isLoggable(cTag, Log.DEBUG)) { Log.d(cTag, "Executing " + selection + " with args " + Arrays.toString(selectionArgs) + " ORDER BY " + orderBy); } Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, null, orderBy); c.setNotificationUri(getContext().getContentResolver(), uri); return c; } protected String getGroupBy(Uri uri) { String groupBy = null; GroupByBuilder builder = groupByBuilders.get(match(uri)); if (builder != null) { groupBy = builder.getGroupBy(uri); } return groupBy; } protected void registerCollectionUrls(String collectionName) { uriMatcher.addURI(authority, collectionName, COLLECTION_MATCH_ID); uriMatcher.addURI(authority, collectionName+"/#", ELEMENT_MATCH_ID); } protected String getContentType() { return ShuffleTable.CONTENT_TYPE_PREFIX+"."+getTableName(); } public String getContentItemType() { return ShuffleTable.CONTENT_ITEM_TYPE_PREFIX+"."+getTableName(); } @Override public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { int count = 0; SQLiteDatabase db = getWriteableDatabase(); count = doUpdate(uri, values, where, whereArgs, db); notifyOnChange(uri); return count; } protected void addRestrictions(Uri uri, SQLiteQueryBuilder qb) { RestrictionBuilder restrictionBuilder = restrictionBuilders.get(match(uri)); if (restrictionBuilder == null) throw new IllegalArgumentException("Unknown URL " + uri); restrictionBuilder.addRestrictions(uri, qb); } public interface CollectionUpdater { int update(Uri uri, ContentValues values, String where, String[] whereArgs, SQLiteDatabase db); } private class EntireCollectionUpdater implements CollectionUpdater { @Override public int update(Uri uri, ContentValues values, String where, String[] whereArgs, SQLiteDatabase db) { return db.update(getTableName(), values, where, whereArgs); } } private class SingleElementUpdater implements CollectionUpdater { @Override public int update(Uri uri, ContentValues values, String where, String[] whereArgs, SQLiteDatabase db) { String segment = uri.getPathSegments().get(1); return db.update(getTableName(), values, "_id=" + segment + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs); } } public interface ElementInserter { Uri insert(Uri url, ContentValues values, SQLiteDatabase db); } protected class ElementInserterImpl implements ElementInserter { private final String primaryKey; public ElementInserterImpl(String primaryKey) { super(); this.primaryKey = primaryKey; } @Override public Uri insert(Uri url, ContentValues values, SQLiteDatabase db) { addDefaultValues(values); long rowID = db.insert(getTableName(), getElementsMap() .get(primaryKey), values); if (rowID > 0) { Uri uri = ContentUris.withAppendedId(contentUri, rowID); notifyOnChange(uri); return uri; } throw new SQLException("Failed to insert row into " + url); } protected void addDefaultValues(ContentValues values) { Long now = System.currentTimeMillis(); if (!values.containsKey(ShuffleTable.MODIFIED_DATE)) { values.put(ShuffleTable.MODIFIED_DATE, now); } if (!values.containsKey(ShuffleTable.DELETED)) { values.put(ShuffleTable.DELETED, 0); } if (!values.containsKey(ShuffleTable.ACTIVE)) { values.put(ShuffleTable.ACTIVE, 1); } if (!values.containsKey(primaryKey)) { values.put(primaryKey, ""); } } } protected Uri doInsert(Uri url, ContentValues values, SQLiteDatabase db) { ElementInserter elementInserter = elementInserters.get(match(url)); if (elementInserter == null) throw new IllegalArgumentException("Unknown URL " + url); return elementInserter.insert(url, values, db); } public static interface ElementDeleter { int delete(Uri uri, String where, String[] whereArgs, SQLiteDatabase db); } private class ElementDeleterImpl implements ElementDeleter { private final String idField; public ElementDeleterImpl(String idField) { super(); this.idField = idField; } @Override public int delete(Uri uri, String where, String[] whereArgs, SQLiteDatabase db) { String id = uri.getPathSegments().get(1); int rowsUpdated = db.delete(getTableName(), idField + "=" + id + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs); notifyOnChange(uri); return rowsUpdated; } } private class EntireCollectionDeleter implements ElementDeleter { @Override public int delete(Uri uri, String where, String[] whereArgs, SQLiteDatabase db) { int rowsUpdated = db.delete(getTableName(), where, whereArgs); notifyOnChange(uri); return rowsUpdated; } } protected int doDelete(Uri uri, String where, String[] whereArgs, SQLiteDatabase db) { ElementDeleter elementDeleter = elementDeleters.get(match(uri)); if (elementDeleter == null) throw new IllegalArgumentException("Unknown uri " + uri); return elementDeleter.delete(uri, where, whereArgs, db); } }