/* * Copyright (C) 2009 Virgil Dobjanschi, Jeff Sharkey, Filip Maelbrancke * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package no.java.schedule.provider; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.provider.BaseColumns; import android.util.Log; import no.java.schedule.provider.SessionsContract.Blocks; import no.java.schedule.provider.SessionsContract.BlocksColumns; import no.java.schedule.provider.SessionsContract.SearchColumns; import no.java.schedule.provider.SessionsContract.Sessions; import no.java.schedule.provider.SessionsContract.SessionsColumns; import no.java.schedule.provider.SessionsContract.Speakers; import no.java.schedule.provider.SessionsContract.SuggestColumns; import no.java.schedule.provider.SessionsContract.Tracks; import no.java.schedule.provider.SessionsContract.TracksColumns; import no.java.schedule.provider.dbutil.DatabaseHelper; import no.java.schedule.provider.dbutil.LookupCache; import java.util.Arrays; import java.util.HashMap; public class SessionsProvider extends ContentProvider { public enum e{ TRACKS ,TRACKS_ID ,TRACKS_VISIBLE , TRACKS_SESSIONS , BLOCKS, BLOCKS_SESSIONS, SESSIONS,SESSIONS_ID,SESSIONS_SEARCH,SUGGEST, SPEAKERS,SPEAKERS_SEARCH; } public static final HashMap<String, String> sSearchProjection = Projections.createSearchProjection(); public static final HashMap<String, String> sSuggestProjection = Projections.createSuggestProjection(); public static final HashMap<String, String> sSpeakersProjection = Projections.createSpeakerProjection(); public static final String TABLE_TRACKS = "tracks"; public static final String TABLE_BLOCKS = "blocks"; public static final String TABLE_SESSIONS = "sessions"; public static final String TABLE_SEARCH = "search"; public static final String TABLE_SUGGEST = "suggest"; public static final String TABLE_SPEAKERS = "speakers"; private static final String TABLE_SESSIONS_JOIN_TRACKS_BLOCKS = "sessions" + " LEFT OUTER JOIN tracks ON sessions.track_id=tracks._id" + " LEFT OUTER JOIN blocks ON sessions.block_id=blocks._id"; private static final String TABLE_SEARCH_JOIN_SESSIONS_TRACKS_BLOCKS = "search " + "LEFT OUTER JOIN sessions ON search.rowid=sessions._id " + "LEFT OUTER JOIN tracks ON sessions.track_id=tracks._id " + "LEFT OUTER JOIN blocks ON sessions.block_id=blocks._id"; private static final String DEFAULT_SORT_ORDER = BlocksColumns.TIME_START + " , " + SessionsColumns.ROOM + " ASC"; private final UriMatcher mUriMatcher = new SessionUriMatcher(); private LookupCache mLookupCache; private SQLiteDatabase readDb; private SQLiteDatabase writeDb; private ContentResolver resolver; public SessionsProvider() { } @Override public boolean onCreate() { try { DatabaseHelper mOpenHelper = new DatabaseHelper(getContext()); readDb = mOpenHelper.getReadableDatabase(); writeDb = mOpenHelper.getWritableDatabase(); mLookupCache = new LookupCache(readDb); resolver = getContext().getContentResolver(); return true; } catch (SQLiteException e){ Log.e("Androidito","Fatal error creating contentprovider",e); return false; } } @Override public Cursor query(Uri notificationUri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { switch (e.values()[mUriMatcher.match(notificationUri)]) { case TRACKS: return queryTrack(projection, selection, selectionArgs, notificationUri ); case TRACKS_VISIBLE: return queryVisibleTracks(projection, selection, selectionArgs, notificationUri ); case TRACKS_SESSIONS: return queryTrackSessions(notificationUri, projection, selection, selectionArgs, notificationUri ); case BLOCKS: return queryBlocks(projection, selection, selectionArgs, notificationUri ); case BLOCKS_SESSIONS: return queryBlockSessions(notificationUri, projection, selection, selectionArgs, notificationUri ); case SESSIONS: return querySessions(projection, selection, selectionArgs, notificationUri ,sortOrder); case SESSIONS_ID: return querySessionId(notificationUri, projection, selection, selectionArgs, notificationUri ); case SESSIONS_SEARCH: return querySessionSearch(notificationUri, projection, selection, selectionArgs, notificationUri ); case SUGGEST: return querySuggest(projection, selectionArgs, notificationUri ); default: throw new IllegalArgumentException("Unknown URL " + notificationUri); } } private Cursor querySuggest(String[] projection, String[] selectionArgs, Uri notificationUri) { SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables(TABLE_SUGGEST); qb.setProjectionMap(sSuggestProjection); qb.appendWhere(SuggestColumns.DISPLAY + " LIKE "); qb.appendWhereEscapeString(selectionArgs[0] + "%"); Cursor c = qb.query(readDb, projection, null, null, null, null, SuggestColumns.DISPLAY + " ASC", "15"); c.setNotificationUri(getContext().getContentResolver(), notificationUri); return c; } private Cursor querySessionSearch(Uri uri, String[] projection, String selection, String[] selectionArgs, Uri notificationUri) { SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); String query = uri.getPathSegments().get(2); qb.setTables(TABLE_SEARCH_JOIN_SESSIONS_TRACKS_BLOCKS); qb.setProjectionMap(sSearchProjection); qb.appendWhere(SearchColumns.INDEX_TEXT + " MATCH "); qb.appendWhereEscapeString(query); Cursor c = qb.query(readDb, projection, selection, selectionArgs, null, null, DEFAULT_SORT_ORDER, null); c.setNotificationUri(getContext().getContentResolver(), notificationUri); return c; } private Cursor querySessionId(Uri uri, String[] projection, String selection, String[] selectionArgs, Uri notificationUri) { SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); long sessionId = ContentUris.parseId(uri); qb.setTables(TABLE_SESSIONS_JOIN_TRACKS_BLOCKS); qb.appendWhere("sessions._id=" + sessionId); Cursor c = qb.query(readDb, projection, selection, selectionArgs, null, null, DEFAULT_SORT_ORDER, null); c.setNotificationUri(getContext().getContentResolver(), notificationUri); return c; } private Cursor querySessions(String[] projection, String selection, String[] selectionArgs, Uri notificationUri, String sortOrder) { SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables(TABLE_SESSIONS_JOIN_TRACKS_BLOCKS); qb.setProjectionMap(Projections.sSessionsProjection); Cursor c = qb.query(readDb, projection, selection, selectionArgs, null, null, sortOrder, null); c.setNotificationUri(getContext().getContentResolver(), notificationUri); return c; } private Cursor queryBlockSessions(Uri uri, String[] projection, String selection, String[] selectionArgs, Uri notificationUri) { SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables(TABLE_SESSIONS_JOIN_TRACKS_BLOCKS); qb.setProjectionMap(Projections.sSessionsProjection); qb.appendWhere("blocks._id=" + uri.getPathSegments().get(1)); Cursor c = qb.query(readDb, projection, selection, selectionArgs, null, null, DEFAULT_SORT_ORDER, null); c.setNotificationUri(getContext().getContentResolver(), notificationUri); return c; } private Cursor queryBlocks(String[] projection, String selection, String[] selectionArgs, Uri notificationUri) { SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables(TABLE_BLOCKS); qb.setProjectionMap(Projections.sBlocksProjection); Cursor c = qb.query(readDb, projection, selection, selectionArgs, null, null, BlocksColumns.TIME_START + " ASC", null); c.setNotificationUri(getContext().getContentResolver(), Blocks.CONTENT_URI); return c; } private Cursor queryTrackSessions(Uri uri, String[] projection, String selection, String[] selectionArgs, Uri notificationUri) { SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables(TABLE_SESSIONS_JOIN_TRACKS_BLOCKS); qb.setProjectionMap(Projections.sSessionsProjection); qb.appendWhere("tracks._id=" + uri.getPathSegments().get(1)); Cursor c = qb.query(readDb, projection, selection, selectionArgs, null, null, BlocksColumns.TIME_START + " ASC", null); c.setNotificationUri(getContext().getContentResolver(), notificationUri); return c; } private Cursor queryVisibleTracks(String[] projection, String selection, String[] selectionArgs, Uri notificationUri) { SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables(TABLE_TRACKS); qb.setProjectionMap(Projections.sTracksProjection); qb.appendWhere(TracksColumns.VISIBLE + "=1"); Cursor c = qb.query(readDb, projection, selection, selectionArgs, null, null, TracksColumns.TRACK + " ASC", null); c.setNotificationUri(getContext().getContentResolver(), notificationUri); return c; } private Cursor queryTrack(String[] projection, String selection, String[] selectionArgs, Uri notificationUri) { String sortOrder; SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables(TABLE_TRACKS); qb.setProjectionMap(Projections.sTracksProjection); sortOrder = TracksColumns.TRACK + " ASC"; Cursor c = qb.query(readDb, projection, selection, selectionArgs, null, null, sortOrder, null); c.setNotificationUri(getContext().getContentResolver(), notificationUri); return c; } @Override public String getType(Uri uri) { switch (e.values()[mUriMatcher.match(uri)]) { case TRACKS: return Tracks.CONTENT_TYPE; case BLOCKS: return Blocks.CONTENT_TYPE; case TRACKS_SESSIONS: case BLOCKS_SESSIONS: case SESSIONS: return Sessions.CONTENT_TYPE; case SESSIONS_ID: return Sessions.CONTENT_ITEM_TYPE; case SPEAKERS: return Speakers.CONTENT_TYPE; default: throw new IllegalArgumentException("Unknown URL " + uri); } } @Override public Uri insert(Uri uri, ContentValues values) { switch (e.values()[mUriMatcher.match(uri)]) { case TRACKS: return insertTracks(values); case BLOCKS: return insertBlock(values); case SESSIONS: return insertSession(values); case SUGGEST: return insertSuggest(uri, values); case SPEAKERS: return insertSpeaker(uri, values); default: throw new SQLException("Failed to insert row into " + uri); } } @Override public int bulkInsert(Uri uri, ContentValues[] values) { switch (e.values()[mUriMatcher.match(uri)]) { case TRACKS: return bulkInsert(TABLE_TRACKS, TracksColumns.TRACK,values); case BLOCKS: return bulkInsert(TABLE_BLOCKS, BlocksColumns.TIME_END,values); case SESSIONS: return bulkInsertSessions(values); case SUGGEST: return bulkInsert(TABLE_SUGGEST,null, values); case SPEAKERS: return bulkInsert(TABLE_SPEAKERS,null, values); default: throw new SQLException("Failed to insert row into " + uri); } } private int bulkInsert(String table,String nullHack,ContentValues[] values) { writeDb.beginTransaction(); try { for (ContentValues value : values) { long trackId = writeDb.insert(table, nullHack, value); } writeDb.setTransactionSuccessful(); } finally { writeDb.endTransaction(); } return values.length; //TODO handle non-perfect situations where not all were inserted } private Uri insertSpeaker(Uri uri, ContentValues values) { writeDb.insert(TABLE_SPEAKERS, null, values); return uri; } private Uri insertSuggest(Uri uri, ContentValues values) { writeDb.insert(TABLE_SUGGEST, null, values); return uri; } private Uri insertSession(ContentValues values) { fillTrackAndBlockValues(values); // Pull out search index before normal insert String indexText = values.getAsString(SearchColumns.INDEX_TEXT); values.remove(SearchColumns.INDEX_TEXT); Cursor existing = readDb.query(TABLE_SESSIONS, new String[]{"_id",SessionsColumns.STARRED}, "sessions." + SessionsColumns.WEB_LINK + " = ?", new String[]{values.getAsString(SessionsColumns.WEB_LINK)}, null, null, null); long sessionId; if (existing.moveToNext()){ int starred = existing.getInt(1); values.put(SessionsColumns.STARRED,starred); sessionId = existing.getLong(0); int updated = writeDb.update(TABLE_SESSIONS,values," _id=?",new String[]{String.valueOf(sessionId)}); } else { sessionId = writeDb.insert(TABLE_SESSIONS, SessionsColumns.TITLE, values); } existing.close(); Uri uri = ContentUris.withAppendedId(Sessions.CONTENT_URI, sessionId); // And now fill search index insertSearchIndex(indexText, sessionId); return uri; } private void insertSearchIndex(String indexText, long sessionId) { ContentValues values = new ContentValues(); values.put("rowid", sessionId); values.put(SearchColumns.INDEX_TEXT, indexText); writeDb.delete(TABLE_SEARCH, " rowid = ?",new String[]{String.valueOf(sessionId)}); writeDb.insert(TABLE_SEARCH, null, values); } private void fillTrackAndBlockValues(ContentValues values) { if (mLookupCache == null) { mLookupCache = new LookupCache(writeDb); } // Replace tracks and blocks with internal table references mLookupCache.fillTrackId(values); mLookupCache.fillBlockId(values); } private int bulkInsertSessions(ContentValues[] values) { writeDb.beginTransaction(); try { for (ContentValues value : values) { insertSession(value); } writeDb.setTransactionSuccessful(); } finally { writeDb.endTransaction(); } return values.length; //TODO handle non-perfect situations where not all were inserted } private Uri insertBlock(ContentValues values) { long blockId = writeDb.insert(TABLE_BLOCKS, BlocksColumns.TIME_END, values); return ContentUris.withAppendedId(Blocks.CONTENT_URI, blockId); } private Uri insertTracks(ContentValues values) { long trackId = writeDb.insert(TABLE_TRACKS, TracksColumns.TRACK, values); return ContentUris.withAppendedId(Tracks.CONTENT_URI, trackId); } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { switch (e.values()[mUriMatcher.match(uri)]) { case TRACKS: return deleteTracks(uri, selection, selectionArgs); case BLOCKS: return deleteBlocks(uri, selection, selectionArgs); case SESSIONS: return deleteSessions(uri, selection, selectionArgs); case SESSIONS_ID: return deleteSessionsId(uri); case SUGGEST: return deleteSuggest(uri, selection, selectionArgs); case SPEAKERS: return deleteSpeakers(uri, selection, selectionArgs); default: throw new IllegalArgumentException("Unknown URL " + uri); } } private int deleteSpeakers(Uri uri, String selection, String[] selectionArgs) { int count; count = writeDb.delete(TABLE_SPEAKERS, selection, selectionArgs); resolver.notifyChange(uri, null, false); return count; } private int deleteSuggest(Uri uri, String selection, String[] selectionArgs) { int count; count = writeDb.delete(TABLE_SUGGEST, selection, selectionArgs); resolver.notifyChange(uri, null, false); return count; } private int deleteSessionsId(Uri uri) { int count; long sessionId = ContentUris.parseId(uri); count = writeDb.delete(TABLE_SESSIONS, BaseColumns._ID + "=" + sessionId, null); resolver.notifyChange(uri, null, false); return count; } private int deleteSessions(Uri uri, String selection, String[] selectionArgs) { int count; count = writeDb.delete(TABLE_SESSIONS, selection, selectionArgs); count += writeDb.delete(TABLE_SEARCH, selection, selectionArgs); resolver.notifyChange(uri, null, false); return count; } private int deleteBlocks(Uri uri, String selection, String[] selectionArgs) { int count; count = writeDb.delete(TABLE_BLOCKS, selection, selectionArgs); resolver.notifyChange(uri, null, false); return count; } private int deleteTracks(Uri uri, String selection, String[] selectionArgs) { int count; count = writeDb.delete(TABLE_TRACKS, selection, selectionArgs); resolver.notifyChange(uri, null, false); return count; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { switch (e.values()[mUriMatcher.match(uri)]) { case TRACKS_ID: return updateTrack(uri, values); case SESSIONS_ID: return updateSession(uri, values); default: throw new IllegalArgumentException("Unknown URL " + uri); } } private int updateSession(Uri uri, ContentValues values) { long sessionId = ContentUris.parseId(uri); int count = writeDb.update(TABLE_SESSIONS, values, BaseColumns._ID + "=" + sessionId, null); getContext().getContentResolver().notifyChange(Sessions.CONTENT_URI, null, false); notifyTrackChange(values); notifyBlockChange(values); return count; } private void notifyBlockChange(ContentValues values) { // Pull out block id if provided if (values.containsKey(SessionsColumns.BLOCK_ID)) { long blockId = values.getAsLong(SessionsColumns.BLOCK_ID); Uri blockUri = Uri.withAppendedPath(ContentUris.withAppendedId(Blocks.CONTENT_URI, blockId), Sessions.CONTENT_DIRECTORY); getContext().getContentResolver().notifyChange(blockUri, null, false); } } private void notifyTrackChange(ContentValues values) { // Pull out track id if provided if (values.containsKey(SessionsColumns.TRACK_ID) ) { long trackId = values.getAsLong(SessionsColumns.TRACK_ID); Uri trackUri = Uri.withAppendedPath( ContentUris.withAppendedId(Tracks.CONTENT_URI, trackId ), Sessions.CONTENT_DIRECTORY); getContext().getContentResolver().notifyChange(trackUri, null, false); } } private int updateTrack(Uri uri, ContentValues values) { int count; long trackId = ContentUris.parseId(uri); count = writeDb.update(TABLE_TRACKS, values, BaseColumns._ID + "=" + trackId, null); getContext().getContentResolver().notifyChange( Tracks.CONTENT_URI, null); return count; } }