/* * Copyright 2012 Google Inc. * * 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 com.google.android.apps.iosched.provider; import com.google.android.apps.iosched.appwidget.ScheduleWidgetProvider; import com.google.android.apps.iosched.provider.ScheduleContract.Announcements; import com.google.android.apps.iosched.provider.ScheduleContract.Blocks; import com.google.android.apps.iosched.provider.ScheduleContract.Feedback; import com.google.android.apps.iosched.provider.ScheduleContract.MapMarkers; import com.google.android.apps.iosched.provider.ScheduleContract.MapTiles; import com.google.android.apps.iosched.provider.ScheduleContract.Rooms; import com.google.android.apps.iosched.provider.ScheduleContract.SearchSuggest; import com.google.android.apps.iosched.provider.ScheduleContract.Sessions; import com.google.android.apps.iosched.provider.ScheduleContract.Speakers; import com.google.android.apps.iosched.provider.ScheduleContract.Tracks; import com.google.android.apps.iosched.provider.ScheduleContract.Sandbox; import com.google.android.apps.iosched.provider.ScheduleDatabase.SessionsSearchColumns; import com.google.android.apps.iosched.provider.ScheduleDatabase.SessionsSpeakers; import com.google.android.apps.iosched.provider.ScheduleDatabase.SessionsTracks; import com.google.android.apps.iosched.provider.ScheduleDatabase.Tables; import com.google.android.apps.iosched.util.SelectionBuilder; import android.app.Activity; import android.app.SearchManager; import android.content.ContentProvider; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentValues; import android.content.Context; import android.content.OperationApplicationException; import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.provider.BaseColumns; import android.text.TextUtils; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static com.google.android.apps.iosched.util.LogUtils.LOGV; import static com.google.android.apps.iosched.util.LogUtils.makeLogTag; /** * Provider that stores {@link ScheduleContract} data. Data is usually inserted * by {@link com.google.android.apps.iosched.sync.SyncHelper}, and queried by various * {@link Activity} instances. */ public class ScheduleProvider extends ContentProvider { private static final String TAG = makeLogTag(ScheduleProvider.class); private ScheduleDatabase mOpenHelper; private static final UriMatcher sUriMatcher = buildUriMatcher(); private static final int BLOCKS = 100; private static final int BLOCKS_BETWEEN = 101; private static final int BLOCKS_ID = 102; private static final int BLOCKS_ID_SESSIONS = 103; private static final int BLOCKS_ID_SESSIONS_STARRED = 104; private static final int TRACKS = 200; private static final int TRACKS_ID = 201; private static final int TRACKS_ID_SESSIONS = 202; private static final int TRACKS_ID_SANDBOX = 203; private static final int ROOMS = 300; private static final int ROOMS_ID = 301; private static final int ROOMS_ID_SESSIONS = 302; private static final int SESSIONS = 400; private static final int SESSIONS_STARRED = 401; private static final int SESSIONS_WITH_TRACK = 402; private static final int SESSIONS_SEARCH = 403; private static final int SESSIONS_AT = 404; private static final int SESSIONS_ID = 405; private static final int SESSIONS_ID_SPEAKERS = 406; private static final int SESSIONS_ID_TRACKS = 407; private static final int SESSIONS_ID_WITH_TRACK = 408; private static final int SESSIONS_ROOM_AFTER = 410; private static final int SPEAKERS = 500; private static final int SPEAKERS_ID = 501; private static final int SPEAKERS_ID_SESSIONS = 502; private static final int SANDBOX = 600; private static final int SANDBOX_SEARCH = 603; private static final int SANDBOX_ID = 604; private static final int ANNOUNCEMENTS = 700; private static final int ANNOUNCEMENTS_ID = 701; private static final int SEARCH_SUGGEST = 800; private static final int SEARCH_INDEX = 801; private static final int MAPMARKERS = 900; private static final int MAPMARKERS_FLOOR = 901; private static final int MAPMARKERS_ID = 902; private static final int MAPTILES = 1000; private static final int MAPTILES_FLOOR = 1001; private static final int FEEDBACK_ALL = 1002; private static final int FEEDBACK_FOR_SESSION = 1003; /** * Build and return a {@link UriMatcher} that catches all {@link Uri} * variations supported by this {@link ContentProvider}. */ private static UriMatcher buildUriMatcher() { final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); final String authority = ScheduleContract.CONTENT_AUTHORITY; matcher.addURI(authority, "blocks", BLOCKS); matcher.addURI(authority, "blocks/between/*/*", BLOCKS_BETWEEN); matcher.addURI(authority, "blocks/*", BLOCKS_ID); matcher.addURI(authority, "blocks/*/sessions", BLOCKS_ID_SESSIONS); matcher.addURI(authority, "blocks/*/sessions/starred", BLOCKS_ID_SESSIONS_STARRED); matcher.addURI(authority, "tracks", TRACKS); matcher.addURI(authority, "tracks/*", TRACKS_ID); matcher.addURI(authority, "tracks/*/sessions", TRACKS_ID_SESSIONS); matcher.addURI(authority, "tracks/*/sandbox", TRACKS_ID_SANDBOX); matcher.addURI(authority, "rooms", ROOMS); matcher.addURI(authority, "rooms/*", ROOMS_ID); matcher.addURI(authority, "rooms/*/sessions", ROOMS_ID_SESSIONS); matcher.addURI(authority, "sessions", SESSIONS); matcher.addURI(authority, "sessions/starred", SESSIONS_STARRED); matcher.addURI(authority, "sessions/with_track", SESSIONS_WITH_TRACK); matcher.addURI(authority, "sessions/search/*", SESSIONS_SEARCH); matcher.addURI(authority, "sessions/at/*", SESSIONS_AT); matcher.addURI(authority, "sessions/room/*/after/*", SESSIONS_ROOM_AFTER); matcher.addURI(authority, "sessions/*", SESSIONS_ID); matcher.addURI(authority, "sessions/*/speakers", SESSIONS_ID_SPEAKERS); matcher.addURI(authority, "sessions/*/tracks", SESSIONS_ID_TRACKS); matcher.addURI(authority, "sessions/*/with_track", SESSIONS_ID_WITH_TRACK); matcher.addURI(authority, "speakers", SPEAKERS); matcher.addURI(authority, "speakers/*", SPEAKERS_ID); matcher.addURI(authority, "speakers/*/sessions", SPEAKERS_ID_SESSIONS); matcher.addURI(authority, "sandbox", SANDBOX); matcher.addURI(authority, "sandbox/search/*", SANDBOX_SEARCH); matcher.addURI(authority, "sandbox/*", SANDBOX_ID); matcher.addURI(authority, "announcements", ANNOUNCEMENTS); matcher.addURI(authority, "announcements/*", ANNOUNCEMENTS_ID); matcher.addURI(authority, "search_suggest_query", SEARCH_SUGGEST); matcher.addURI(authority, "search_index", SEARCH_INDEX); // 'update' only matcher.addURI(authority, "mapmarkers", MAPMARKERS); matcher.addURI(authority, "mapmarkers/floor/*", MAPMARKERS_FLOOR); matcher.addURI(authority, "mapmarkers/*", MAPMARKERS_ID); matcher.addURI(authority, "maptiles", MAPTILES); matcher.addURI(authority, "maptiles/*", MAPTILES_FLOOR); matcher.addURI(authority, "feedback/*", FEEDBACK_FOR_SESSION); matcher.addURI(authority, "feedback*", FEEDBACK_ALL); matcher.addURI(authority, "feedback", FEEDBACK_ALL); return matcher; } @Override public boolean onCreate() { mOpenHelper = new ScheduleDatabase(getContext()); return true; } private void deleteDatabase() { // TODO: wait for content provider operations to finish, then tear down mOpenHelper.close(); Context context = getContext(); ScheduleDatabase.deleteDatabase(context); mOpenHelper = new ScheduleDatabase(getContext()); } /** {@inheritDoc} */ @Override public String getType(Uri uri) { final int match = sUriMatcher.match(uri); switch (match) { case BLOCKS: return Blocks.CONTENT_TYPE; case BLOCKS_BETWEEN: return Blocks.CONTENT_TYPE; case BLOCKS_ID: return Blocks.CONTENT_ITEM_TYPE; case BLOCKS_ID_SESSIONS: return Sessions.CONTENT_TYPE; case BLOCKS_ID_SESSIONS_STARRED: return Sessions.CONTENT_TYPE; case TRACKS: return Tracks.CONTENT_TYPE; case TRACKS_ID: return Tracks.CONTENT_ITEM_TYPE; case TRACKS_ID_SESSIONS: return Sessions.CONTENT_TYPE; case TRACKS_ID_SANDBOX: return Sandbox.CONTENT_TYPE; case ROOMS: return Rooms.CONTENT_TYPE; case ROOMS_ID: return Rooms.CONTENT_ITEM_TYPE; case ROOMS_ID_SESSIONS: return Sessions.CONTENT_TYPE; case SESSIONS: return Sessions.CONTENT_TYPE; case SESSIONS_STARRED: return Sessions.CONTENT_TYPE; case SESSIONS_WITH_TRACK: return Sessions.CONTENT_TYPE; case SESSIONS_SEARCH: return Sessions.CONTENT_TYPE; case SESSIONS_AT: return Sessions.CONTENT_TYPE; case SESSIONS_ID: return Sessions.CONTENT_ITEM_TYPE; case SESSIONS_ID_SPEAKERS: return Speakers.CONTENT_TYPE; case SESSIONS_ID_TRACKS: return Tracks.CONTENT_TYPE; case SESSIONS_ID_WITH_TRACK: return Sessions.CONTENT_TYPE; case SESSIONS_ROOM_AFTER: return Sessions.CONTENT_TYPE; case SPEAKERS: return Speakers.CONTENT_TYPE; case SPEAKERS_ID: return Speakers.CONTENT_ITEM_TYPE; case SPEAKERS_ID_SESSIONS: return Sessions.CONTENT_TYPE; case SANDBOX: return Sandbox.CONTENT_TYPE; case SANDBOX_SEARCH: return ScheduleContract.Sandbox.CONTENT_TYPE; case SANDBOX_ID: return ScheduleContract.Sandbox.CONTENT_ITEM_TYPE; case ANNOUNCEMENTS: return Announcements.CONTENT_TYPE; case ANNOUNCEMENTS_ID: return Announcements.CONTENT_ITEM_TYPE; case MAPMARKERS: return MapMarkers.CONTENT_TYPE; case MAPMARKERS_FLOOR: return MapMarkers.CONTENT_TYPE; case MAPMARKERS_ID: return MapMarkers.CONTENT_ITEM_TYPE; case MAPTILES: return MapTiles.CONTENT_TYPE; case MAPTILES_FLOOR: return MapTiles.CONTENT_ITEM_TYPE; case FEEDBACK_FOR_SESSION: return Feedback.CONTENT_ITEM_TYPE; case FEEDBACK_ALL: return Feedback.CONTENT_TYPE; default: throw new UnsupportedOperationException("Unknown uri: " + uri); } } /** {@inheritDoc} */ @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { LOGV(TAG, "query(uri=" + uri + ", proj=" + Arrays.toString(projection) + ")"); final SQLiteDatabase db = mOpenHelper.getReadableDatabase(); String uriFilter = uri.getQueryParameter(Sessions.QUERY_PARAMETER_FILTER); final int match = sUriMatcher.match(uri); switch (match) { default: { // Most cases are handled with simple SelectionBuilder final SelectionBuilder builder = buildExpandedSelection(uri, match); // If a special filter was specified, try to apply it if (!TextUtils.isEmpty(uriFilter)) { if (Sessions.QUERY_VALUE_FILTER_SESSIONS_CODELABS_ONLY.equals(uriFilter)) { builder.where(Sessions.SESSION_TYPE + " NOT IN ('" + Sessions.SESSION_TYPE_OFFICE_HOURS + "','" + Sessions.SESSION_TYPE_KEYNOTE + "')"); } else if (Sessions.QUERY_VALUE_FILTER_OFFICE_HOURS_ONLY.equals(uriFilter)) { builder.where(Sessions.SESSION_TYPE + " = ?", Sessions.SESSION_TYPE_OFFICE_HOURS); } } return builder.where(selection, selectionArgs).query(db, projection, sortOrder); } case SEARCH_SUGGEST: { final SelectionBuilder builder = new SelectionBuilder(); // Adjust incoming query to become SQL text match selectionArgs[0] = selectionArgs[0] + "%"; builder.table(Tables.SEARCH_SUGGEST); builder.where(selection, selectionArgs); builder.map(SearchManager.SUGGEST_COLUMN_QUERY, SearchManager.SUGGEST_COLUMN_TEXT_1); projection = new String[] { BaseColumns._ID, SearchManager.SUGGEST_COLUMN_TEXT_1, SearchManager.SUGGEST_COLUMN_QUERY }; final String limit = uri.getQueryParameter(SearchManager.SUGGEST_PARAMETER_LIMIT); return builder.query(db, projection, null, null, SearchSuggest.DEFAULT_SORT, limit); } } } /** {@inheritDoc} */ @Override public Uri insert(Uri uri, ContentValues values) { LOGV(TAG, "insert(uri=" + uri + ", values=" + values.toString() + ")"); final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); final int match = sUriMatcher.match(uri); boolean syncToNetwork = !ScheduleContract.hasCallerIsSyncAdapterParameter(uri); switch (match) { case BLOCKS: { db.insertOrThrow(Tables.BLOCKS, null, values); notifyChange(uri, syncToNetwork); return Blocks.buildBlockUri(values.getAsString(Blocks.BLOCK_ID)); } case TRACKS: { db.insertOrThrow(Tables.TRACKS, null, values); notifyChange(uri, syncToNetwork); return Tracks.buildTrackUri(values.getAsString(Tracks.TRACK_ID)); } case ROOMS: { db.insertOrThrow(Tables.ROOMS, null, values); notifyChange(uri, syncToNetwork); return Rooms.buildRoomUri(values.getAsString(Rooms.ROOM_ID)); } case SESSIONS: { db.insertOrThrow(Tables.SESSIONS, null, values); notifyChange(uri, syncToNetwork); return Sessions.buildSessionUri(values.getAsString(Sessions.SESSION_ID)); } case SESSIONS_ID_SPEAKERS: { db.insertOrThrow(Tables.SESSIONS_SPEAKERS, null, values); notifyChange(uri, syncToNetwork); return Speakers.buildSpeakerUri(values.getAsString(SessionsSpeakers.SPEAKER_ID)); } case SESSIONS_ID_TRACKS: { db.insertOrThrow(Tables.SESSIONS_TRACKS, null, values); notifyChange(uri, syncToNetwork); return Tracks.buildTrackUri(values.getAsString(SessionsTracks.TRACK_ID)); } case SPEAKERS: { db.insertOrThrow(Tables.SPEAKERS, null, values); notifyChange(uri, syncToNetwork); return Speakers.buildSpeakerUri(values.getAsString(Speakers.SPEAKER_ID)); } case SANDBOX: { db.insertOrThrow(Tables.SANDBOX, null, values); notifyChange(uri, syncToNetwork); return Sandbox.buildCompanyUri(values.getAsString(Sandbox.COMPANY_ID)); } case ANNOUNCEMENTS: { db.insertOrThrow(Tables.ANNOUNCEMENTS, null, values); notifyChange(uri, syncToNetwork); return Announcements.buildAnnouncementUri(values .getAsString(Announcements.ANNOUNCEMENT_ID)); } case SEARCH_SUGGEST: { db.insertOrThrow(Tables.SEARCH_SUGGEST, null, values); notifyChange(uri, syncToNetwork); return SearchSuggest.CONTENT_URI; } case MAPMARKERS: { db.insertOrThrow(Tables.MAPMARKERS, null, values); notifyChange(uri, syncToNetwork); return MapMarkers.buildMarkerUri(values.getAsString(MapMarkers.MARKER_ID)); } case MAPTILES: { db.insertOrThrow(Tables.MAPTILES, null, values); notifyChange(uri, syncToNetwork); return MapTiles.buildFloorUri(values.getAsString(MapTiles.TILE_FLOOR)); } case FEEDBACK_FOR_SESSION: { db.insertOrThrow(Tables.FEEDBACK, null, values); notifyChange(uri, syncToNetwork); return Feedback.buildFeedbackUri(values.getAsString(Feedback.SESSION_ID)); } default: { throw new UnsupportedOperationException("Unknown uri: " + uri); } } } /** {@inheritDoc} */ @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { LOGV(TAG, "update(uri=" + uri + ", values=" + values.toString() + ")"); final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); final int match = sUriMatcher.match(uri); if (match == SEARCH_INDEX) { // update the search index ScheduleDatabase.updateSessionSearchIndex(db); return 1; } final SelectionBuilder builder = buildSimpleSelection(uri); int retVal = builder.where(selection, selectionArgs).update(db, values); boolean syncToNetwork = !ScheduleContract.hasCallerIsSyncAdapterParameter(uri); notifyChange(uri, syncToNetwork); return retVal; } /** {@inheritDoc} */ @Override public int delete(Uri uri, String selection, String[] selectionArgs) { LOGV(TAG, "delete(uri=" + uri + ")"); if (uri == ScheduleContract.BASE_CONTENT_URI) { // Handle whole database deletes (e.g. when signing out) deleteDatabase(); notifyChange(uri, false); return 1; } final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); final SelectionBuilder builder = buildSimpleSelection(uri); int retVal = builder.where(selection, selectionArgs).delete(db); notifyChange(uri, !ScheduleContract.hasCallerIsSyncAdapterParameter(uri)); return retVal; } private void notifyChange(Uri uri, boolean syncToNetwork) { Context context = getContext(); context.getContentResolver().notifyChange(uri, null, syncToNetwork); // Widgets can't register content observers so we refresh widgets separately. context.sendBroadcast(ScheduleWidgetProvider.getRefreshBroadcastIntent(context, false)); } /** * Apply the given set of {@link ContentProviderOperation}, executing inside * a {@link SQLiteDatabase} transaction. All changes will be rolled back if * any single one fails. */ @Override public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) throws OperationApplicationException { final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); db.beginTransaction(); try { final int numOperations = operations.size(); final ContentProviderResult[] results = new ContentProviderResult[numOperations]; for (int i = 0; i < numOperations; i++) { results[i] = operations.get(i).apply(this, results, i); } db.setTransactionSuccessful(); return results; } finally { db.endTransaction(); } } /** * Build a simple {@link SelectionBuilder} to match the requested * {@link Uri}. This is usually enough to support {@link #insert}, * {@link #update}, and {@link #delete} operations. */ private SelectionBuilder buildSimpleSelection(Uri uri) { final SelectionBuilder builder = new SelectionBuilder(); final int match = sUriMatcher.match(uri); switch (match) { case BLOCKS: { return builder.table(Tables.BLOCKS); } case BLOCKS_ID: { final String blockId = Blocks.getBlockId(uri); return builder.table(Tables.BLOCKS) .where(Blocks.BLOCK_ID + "=?", blockId); } case TRACKS: { return builder.table(Tables.TRACKS); } case TRACKS_ID: { final String trackId = Tracks.getTrackId(uri); return builder.table(Tables.TRACKS) .where(Tracks.TRACK_ID + "=?", trackId); } case ROOMS: { return builder.table(Tables.ROOMS); } case ROOMS_ID: { final String roomId = Rooms.getRoomId(uri); return builder.table(Tables.ROOMS) .where(Rooms.ROOM_ID + "=?", roomId); } case SESSIONS: { return builder.table(Tables.SESSIONS); } case SESSIONS_ID: { final String sessionId = Sessions.getSessionId(uri); return builder.table(Tables.SESSIONS) .where(Sessions.SESSION_ID + "=?", sessionId); } case SESSIONS_ID_SPEAKERS: { final String sessionId = Sessions.getSessionId(uri); return builder.table(Tables.SESSIONS_SPEAKERS) .where(Sessions.SESSION_ID + "=?", sessionId); } case SESSIONS_ID_TRACKS: { final String sessionId = Sessions.getSessionId(uri); return builder.table(Tables.SESSIONS_TRACKS) .where(Sessions.SESSION_ID + "=?", sessionId); } case SPEAKERS: { return builder.table(Tables.SPEAKERS); } case SPEAKERS_ID: { final String speakerId = Speakers.getSpeakerId(uri); return builder.table(Tables.SPEAKERS) .where(Speakers.SPEAKER_ID + "=?", speakerId); } case SANDBOX: { return builder.table(Tables.SANDBOX); } case SANDBOX_ID: { final String companyId = ScheduleContract.Sandbox.getCompanyId(uri); return builder.table(Tables.SANDBOX) .where(Sandbox.COMPANY_ID + "=?", companyId); } case ANNOUNCEMENTS: { return builder.table(Tables.ANNOUNCEMENTS); } case ANNOUNCEMENTS_ID: { final String announcementId = Announcements.getAnnouncementId(uri); return builder.table(Tables.ANNOUNCEMENTS) .where(Announcements.ANNOUNCEMENT_ID + "=?", announcementId); } case MAPMARKERS: { return builder.table(Tables.MAPMARKERS); } case MAPMARKERS_FLOOR: { final String floor = MapMarkers.getMarkerFloor(uri); return builder.table(Tables.MAPMARKERS) .where(MapMarkers.MARKER_FLOOR+ "=?", floor); } case MAPMARKERS_ID: { final String markerId = MapMarkers.getMarkerId(uri); return builder.table(Tables.MAPMARKERS) .where(MapMarkers.MARKER_ID+ "=?", markerId); } case MAPTILES: { return builder.table(Tables.MAPTILES); } case MAPTILES_FLOOR: { final String floor = MapTiles.getFloorId(uri); return builder.table(Tables.MAPTILES) .where(MapTiles.TILE_FLOOR+ "=?", floor); } case SEARCH_SUGGEST: { return builder.table(Tables.SEARCH_SUGGEST); } case FEEDBACK_FOR_SESSION: { final String session_id = Feedback.getSessionId(uri); return builder.table(Tables.FEEDBACK) .where(Feedback.SESSION_ID + "=?", session_id); } case FEEDBACK_ALL: { return builder.table(Tables.FEEDBACK); } default: { throw new UnsupportedOperationException("Unknown uri for " + match + ": " + uri); } } } /** * Build an advanced {@link SelectionBuilder} to match the requested * {@link Uri}. This is usually only used by {@link #query}, since it * performs table joins useful for {@link Cursor} data. */ private SelectionBuilder buildExpandedSelection(Uri uri, int match) { final SelectionBuilder builder = new SelectionBuilder(); switch (match) { case BLOCKS: { return builder .table(Tables.BLOCKS) .map(Blocks.SESSIONS_COUNT, Subquery.BLOCK_SESSIONS_COUNT) .map(Blocks.NUM_STARRED_SESSIONS, Subquery.BLOCK_NUM_STARRED_SESSIONS) .map(Blocks.NUM_LIVESTREAMED_SESSIONS, Subquery.BLOCK_NUM_LIVESTREAMED_SESSIONS) .map(Blocks.STARRED_SESSION_ID, Subquery.BLOCK_STARRED_SESSION_ID) .map(Blocks.STARRED_SESSION_TITLE, Subquery.BLOCK_STARRED_SESSION_TITLE) .map(Blocks.STARRED_SESSION_HASHTAGS, Subquery.BLOCK_STARRED_SESSION_HASHTAGS) .map(Blocks.STARRED_SESSION_URL, Subquery.BLOCK_STARRED_SESSION_URL) .map(Blocks.STARRED_SESSION_LIVESTREAM_URL, Subquery.BLOCK_STARRED_SESSION_LIVESTREAM_URL) .map(Blocks.STARRED_SESSION_ROOM_NAME, Subquery.BLOCK_STARRED_SESSION_ROOM_NAME) .map(Blocks.STARRED_SESSION_ROOM_ID, Subquery.BLOCK_STARRED_SESSION_ROOM_ID); } case BLOCKS_BETWEEN: { final List<String> segments = uri.getPathSegments(); final String startTime = segments.get(2); final String endTime = segments.get(3); return builder.table(Tables.BLOCKS) .map(Blocks.SESSIONS_COUNT, Subquery.BLOCK_SESSIONS_COUNT) .map(Blocks.NUM_STARRED_SESSIONS, Subquery.BLOCK_NUM_STARRED_SESSIONS) .map(Blocks.NUM_LIVESTREAMED_SESSIONS, Subquery.BLOCK_NUM_LIVESTREAMED_SESSIONS) .where(Blocks.BLOCK_START + ">=?", startTime) .where(Blocks.BLOCK_START + "<=?", endTime); } case BLOCKS_ID: { final String blockId = Blocks.getBlockId(uri); return builder.table(Tables.BLOCKS) .map(Blocks.SESSIONS_COUNT, Subquery.BLOCK_SESSIONS_COUNT) .map(Blocks.NUM_STARRED_SESSIONS, Subquery.BLOCK_NUM_STARRED_SESSIONS) .map(Blocks.NUM_LIVESTREAMED_SESSIONS, Subquery.BLOCK_NUM_LIVESTREAMED_SESSIONS) .where(Blocks.BLOCK_ID + "=?", blockId); } case BLOCKS_ID_SESSIONS: { final String blockId = Blocks.getBlockId(uri); return builder.table(Tables.SESSIONS_JOIN_BLOCKS_ROOMS) .map(Blocks.SESSIONS_COUNT, Subquery.BLOCK_SESSIONS_COUNT) .map(Blocks.NUM_STARRED_SESSIONS, Subquery.BLOCK_NUM_STARRED_SESSIONS) .map(Blocks.NUM_LIVESTREAMED_SESSIONS, Subquery.BLOCK_NUM_LIVESTREAMED_SESSIONS) .mapToTable(Sessions._ID, Tables.SESSIONS) .mapToTable(Sessions.SESSION_ID, Tables.SESSIONS) .mapToTable(Sessions.BLOCK_ID, Tables.SESSIONS) .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS) .where(Qualified.SESSIONS_BLOCK_ID + "=?", blockId); } case BLOCKS_ID_SESSIONS_STARRED: { final String blockId = Blocks.getBlockId(uri); return builder.table(Tables.SESSIONS_JOIN_BLOCKS_ROOMS) .map(Blocks.SESSIONS_COUNT, Subquery.BLOCK_SESSIONS_COUNT) .map(Blocks.NUM_STARRED_SESSIONS, Subquery.BLOCK_NUM_STARRED_SESSIONS) .map(Blocks.NUM_LIVESTREAMED_SESSIONS, Subquery.BLOCK_NUM_LIVESTREAMED_SESSIONS) .mapToTable(Sessions._ID, Tables.SESSIONS) .mapToTable(Sessions.SESSION_ID, Tables.SESSIONS) .mapToTable(Sessions.BLOCK_ID, Tables.SESSIONS) .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS) .where(Qualified.SESSIONS_BLOCK_ID + "=?", blockId) .where(Qualified.SESSIONS_STARRED + "=1"); } case TRACKS: { return builder.table(Tables.TRACKS) .map(Tracks.SESSIONS_COUNT, Subquery.TRACK_SESSIONS_COUNT) .map(Tracks.OFFICE_HOURS_COUNT, Subquery.TRACK_OFFICE_HOURS_COUNT) .map(Tracks.SANDBOX_COUNT, Subquery.TRACK_SANDBOX_COUNT); } case TRACKS_ID: { final String trackId = Tracks.getTrackId(uri); return builder.table(Tables.TRACKS) .where(Tracks.TRACK_ID + "=?", trackId); } case TRACKS_ID_SESSIONS: { final String trackId = Tracks.getTrackId(uri); return builder.table(Tables.SESSIONS_TRACKS_JOIN_SESSIONS_BLOCKS_ROOMS) .mapToTable(Sessions._ID, Tables.SESSIONS) .mapToTable(Sessions.SESSION_ID, Tables.SESSIONS) .mapToTable(Sessions.BLOCK_ID, Tables.SESSIONS) .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS) .where(Qualified.SESSIONS_TRACKS_TRACK_ID + "=?", trackId); } case TRACKS_ID_SANDBOX: { final String trackId = Tracks.getTrackId(uri); return builder.table(Tables.SANDBOX_JOIN_TRACKS_BLOCKS_ROOMS) .mapToTable(ScheduleContract.Sandbox._ID, Tables.SANDBOX) .mapToTable(Sandbox.TRACK_ID, Tables.SANDBOX) .mapToTable(ScheduleContract.Sandbox.BLOCK_ID, Tables.BLOCKS) .mapToTable(Sandbox.ROOM_ID, Tables.ROOMS) .where(Qualified.SANDBOX_TRACK_ID + "=?", trackId); } case ROOMS: { return builder.table(Tables.ROOMS); } case ROOMS_ID: { final String roomId = Rooms.getRoomId(uri); return builder.table(Tables.ROOMS) .where(Rooms.ROOM_ID + "=?", roomId); } case ROOMS_ID_SESSIONS: { final String roomId = Rooms.getRoomId(uri); return builder.table(Tables.SESSIONS_JOIN_BLOCKS_ROOMS) .mapToTable(Sessions._ID, Tables.SESSIONS) .mapToTable(Sessions.BLOCK_ID, Tables.SESSIONS) .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS) .where(Qualified.SESSIONS_ROOM_ID + "=?", roomId); } case SESSIONS: { return builder.table(Tables.SESSIONS_JOIN_BLOCKS_ROOMS) .mapToTable(Sessions._ID, Tables.SESSIONS) .mapToTable(Sessions.BLOCK_ID, Tables.SESSIONS) .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS); } case SESSIONS_STARRED: { return builder.table(Tables.SESSIONS_JOIN_BLOCKS_ROOMS) .mapToTable(Sessions._ID, Tables.SESSIONS) .mapToTable(Sessions.BLOCK_ID, Tables.SESSIONS) .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS) .where(Sessions.SESSION_STARRED + "=1"); } case SESSIONS_WITH_TRACK: { return builder.table(Tables.SESSIONS_JOIN_TRACKS_JOIN_BLOCKS) .mapToTable(Sessions._ID, Tables.SESSIONS) .mapToTable(Sessions.SESSION_ID, Tables.SESSIONS) .mapToTable(Tracks.TRACK_ID, Tables.TRACKS) .mapToTable(Tracks.TRACK_NAME, Tables.TRACKS) .mapToTable(Blocks.BLOCK_ID, Tables.BLOCKS); } case SESSIONS_ID_WITH_TRACK: { final String sessionId = Sessions.getSessionId(uri); return builder.table(Tables.SESSIONS_JOIN_TRACKS_JOIN_BLOCKS) .mapToTable(Sessions._ID, Tables.SESSIONS) .mapToTable(Sessions.SESSION_ID, Tables.SESSIONS) .mapToTable(Tracks.TRACK_ID, Tables.TRACKS) .mapToTable(Tracks.TRACK_NAME, Tables.TRACKS) .mapToTable(Blocks.BLOCK_ID, Tables.BLOCKS) .where(Qualified.SESSIONS_SESSION_ID + "=?", sessionId); } case SESSIONS_SEARCH: { final String query = Sessions.getSearchQuery(uri); return builder.table(Tables.SESSIONS_SEARCH_JOIN_SESSIONS_BLOCKS_ROOMS) .map(Sessions.SEARCH_SNIPPET, Subquery.SESSIONS_SNIPPET) .mapToTable(Sessions._ID, Tables.SESSIONS) .mapToTable(Sessions.SESSION_ID, Tables.SESSIONS) .mapToTable(Sessions.BLOCK_ID, Tables.SESSIONS) .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS) .where(SessionsSearchColumns.BODY + " MATCH ?", query); } case SESSIONS_AT: { final List<String> segments = uri.getPathSegments(); final String time = segments.get(2); return builder.table(Tables.SESSIONS_JOIN_BLOCKS_ROOMS) .mapToTable(Sessions._ID, Tables.SESSIONS) .mapToTable(Sessions.BLOCK_ID, Tables.SESSIONS) .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS) .where(Sessions.BLOCK_START + "<=?", time) .where(Sessions.BLOCK_END + ">=?", time); } case SESSIONS_ID: { final String sessionId = Sessions.getSessionId(uri); return builder.table(Tables.SESSIONS_JOIN_BLOCKS_ROOMS) .mapToTable(Sessions._ID, Tables.SESSIONS) .mapToTable(Sessions.BLOCK_ID, Tables.SESSIONS) .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS) .where(Qualified.SESSIONS_SESSION_ID + "=?", sessionId); } case SESSIONS_ID_SPEAKERS: { final String sessionId = Sessions.getSessionId(uri); return builder.table(Tables.SESSIONS_SPEAKERS_JOIN_SPEAKERS) .mapToTable(Speakers._ID, Tables.SPEAKERS) .mapToTable(Speakers.SPEAKER_ID, Tables.SPEAKERS) .where(Qualified.SESSIONS_SPEAKERS_SESSION_ID + "=?", sessionId); } case SESSIONS_ID_TRACKS: { final String sessionId = Sessions.getSessionId(uri); return builder.table(Tables.SESSIONS_TRACKS_JOIN_TRACKS) .mapToTable(Tracks._ID, Tables.TRACKS) .mapToTable(Tracks.TRACK_ID, Tables.TRACKS) .where(Qualified.SESSIONS_TRACKS_SESSION_ID + "=?", sessionId); } case SESSIONS_ROOM_AFTER: { final String room = Sessions.getRoom(uri); final String time = Sessions.getAfter(uri); return builder.table(Tables.SESSIONS_JOIN_BLOCKS_ROOMS) .mapToTable(Sessions._ID, Tables.SESSIONS) .mapToTable(Sessions.BLOCK_ID, Tables.SESSIONS) .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS) .where(Qualified.SESSIONS_ROOM_ID+ "=?", room) .where("("+Sessions.BLOCK_START + "<= ? AND "+Sessions.BLOCK_END+">= ?) OR ("+Sessions.BLOCK_START+" >= ?)", time,time,time); } case SPEAKERS: { return builder.table(Tables.SPEAKERS); } case SPEAKERS_ID: { final String speakerId = Speakers.getSpeakerId(uri); return builder.table(Tables.SPEAKERS) .where(Speakers.SPEAKER_ID + "=?", speakerId); } case SPEAKERS_ID_SESSIONS: { final String speakerId = Speakers.getSpeakerId(uri); return builder.table(Tables.SESSIONS_SPEAKERS_JOIN_SESSIONS_BLOCKS_ROOMS) .mapToTable(Sessions._ID, Tables.SESSIONS) .mapToTable(Sessions.SESSION_ID, Tables.SESSIONS) .mapToTable(Sessions.BLOCK_ID, Tables.SESSIONS) .mapToTable(Sessions.ROOM_ID, Tables.SESSIONS) .where(Qualified.SESSIONS_SPEAKERS_SPEAKER_ID + "=?", speakerId); } case SANDBOX: { return builder.table(Tables.SANDBOX_JOIN_TRACKS_BLOCKS_ROOMS) .mapToTable(Sandbox._ID, Tables.SANDBOX) .mapToTable(Sandbox.TRACK_ID, Tables.SANDBOX) .mapToTable(Sandbox.BLOCK_ID, Tables.SANDBOX) .mapToTable(Sandbox.ROOM_ID, Tables.SANDBOX); } case SANDBOX_ID: { final String companyId = ScheduleContract.Sandbox.getCompanyId(uri); return builder.table(Tables.SANDBOX_JOIN_TRACKS_BLOCKS_ROOMS) .mapToTable(ScheduleContract.Sandbox._ID, Tables.SANDBOX) .mapToTable(Sandbox.TRACK_ID, Tables.SANDBOX) .mapToTable(Sandbox.BLOCK_ID, Tables.SANDBOX) .mapToTable(Sandbox.ROOM_ID, Tables.SANDBOX) .where(Sandbox.COMPANY_ID + "=?", companyId); } case ANNOUNCEMENTS: { return builder.table(Tables.ANNOUNCEMENTS); } case ANNOUNCEMENTS_ID: { final String announcementId = Announcements.getAnnouncementId(uri); return builder.table(Tables.ANNOUNCEMENTS) .where(Announcements.ANNOUNCEMENT_ID + "=?", announcementId); } case MAPMARKERS: { return builder.table(Tables.MAPMARKERS); } case MAPMARKERS_FLOOR: { final String floor = MapMarkers.getMarkerFloor(uri); return builder.table(Tables.MAPMARKERS) .where(MapMarkers.MARKER_FLOOR+ "=?", floor); } case MAPMARKERS_ID: { final String roomId = MapMarkers.getMarkerId(uri); return builder.table(Tables.MAPMARKERS) .where(MapMarkers.MARKER_ID+ "=?", roomId); } case MAPTILES: { return builder.table(Tables.MAPTILES); } case MAPTILES_FLOOR: { final String floor = MapTiles.getFloorId(uri); return builder.table(Tables.MAPTILES) .where(MapTiles.TILE_FLOOR+ "=?", floor); } case FEEDBACK_FOR_SESSION: { final String sessionId = Feedback.getSessionId(uri); return builder.table(Tables.FEEDBACK) .where(Feedback.SESSION_ID + "=?", sessionId); } case FEEDBACK_ALL: { return builder.table(Tables.FEEDBACK); } default: { throw new UnsupportedOperationException("Unknown uri: " + uri); } } } @Override public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { final int match = sUriMatcher.match(uri); switch (match) { default: { throw new UnsupportedOperationException("Unknown uri: " + uri); } } } private interface Subquery { String BLOCK_SESSIONS_COUNT = "(SELECT COUNT(" + Qualified.SESSIONS_SESSION_ID + ") FROM " + Tables.SESSIONS + " WHERE " + Qualified.SESSIONS_BLOCK_ID + "=" + Qualified.BLOCKS_BLOCK_ID + ")"; String BLOCK_NUM_STARRED_SESSIONS = "(SELECT COUNT(1) FROM " + Tables.SESSIONS + " WHERE " + Qualified.SESSIONS_BLOCK_ID + "=" + Qualified.BLOCKS_BLOCK_ID + " AND " + Qualified.SESSIONS_STARRED + "=1)"; String BLOCK_NUM_LIVESTREAMED_SESSIONS = "(SELECT COUNT(1) FROM " + Tables.SESSIONS + " WHERE " + Qualified.SESSIONS_BLOCK_ID + "=" + Qualified.BLOCKS_BLOCK_ID + " AND IFNULL(" + Qualified.SESSIONS_LIVESTREAM_URL + ",'')!='')"; String BLOCK_STARRED_SESSION_ID = "(SELECT " + Qualified.SESSIONS_SESSION_ID + " FROM " + Tables.SESSIONS + " WHERE " + Qualified.SESSIONS_BLOCK_ID + "=" + Qualified.BLOCKS_BLOCK_ID + " AND " + Qualified.SESSIONS_STARRED + "=1 " + "ORDER BY " + Qualified.SESSIONS_TITLE + ")"; String BLOCK_STARRED_SESSION_TITLE = "(SELECT " + Qualified.SESSIONS_TITLE + " FROM " + Tables.SESSIONS + " WHERE " + Qualified.SESSIONS_BLOCK_ID + "=" + Qualified.BLOCKS_BLOCK_ID + " AND " + Qualified.SESSIONS_STARRED + "=1 " + "ORDER BY " + Qualified.SESSIONS_TITLE + ")"; String BLOCK_STARRED_SESSION_HASHTAGS = "(SELECT " + Qualified.SESSIONS_HASHTAGS + " FROM " + Tables.SESSIONS + " WHERE " + Qualified.SESSIONS_BLOCK_ID + "=" + Qualified.BLOCKS_BLOCK_ID + " AND " + Qualified.SESSIONS_STARRED + "=1 " + "ORDER BY " + Qualified.SESSIONS_TITLE + ")"; String BLOCK_STARRED_SESSION_URL = "(SELECT " + Qualified.SESSIONS_URL + " FROM " + Tables.SESSIONS + " WHERE " + Qualified.SESSIONS_BLOCK_ID + "=" + Qualified.BLOCKS_BLOCK_ID + " AND " + Qualified.SESSIONS_STARRED + "=1 " + "ORDER BY " + Qualified.SESSIONS_TITLE + ")"; String BLOCK_STARRED_SESSION_LIVESTREAM_URL = "(SELECT " + Qualified.SESSIONS_LIVESTREAM_URL + " FROM " + Tables.SESSIONS + " WHERE " + Qualified.SESSIONS_BLOCK_ID + "=" + Qualified.BLOCKS_BLOCK_ID + " AND " + Qualified.SESSIONS_STARRED + "=1 " + "ORDER BY " + Qualified.SESSIONS_TITLE + ")"; String BLOCK_STARRED_SESSION_ROOM_NAME = "(SELECT " + Qualified.ROOMS_ROOM_NAME + " FROM " + Tables.SESSIONS_JOIN_ROOMS + " WHERE " + Qualified.SESSIONS_BLOCK_ID + "=" + Qualified.BLOCKS_BLOCK_ID + " AND " + Qualified.SESSIONS_STARRED + "=1 " + "ORDER BY " + Qualified.SESSIONS_TITLE + ")"; String BLOCK_STARRED_SESSION_ROOM_ID = "(SELECT " + Qualified.ROOMS_ROOM_ID + " FROM " + Tables.SESSIONS_JOIN_ROOMS + " WHERE " + Qualified.SESSIONS_BLOCK_ID + "=" + Qualified.BLOCKS_BLOCK_ID + " AND " + Qualified.SESSIONS_STARRED + "=1 " + "ORDER BY " + Qualified.SESSIONS_TITLE + ")"; String TRACK_SESSIONS_COUNT = "(SELECT COUNT(" + Qualified.SESSIONS_TRACKS_SESSION_ID + ") FROM " + Tables.SESSIONS_TRACKS + " INNER JOIN " + Tables.SESSIONS + " ON " + Qualified.SESSIONS_SESSION_ID + "=" + Qualified.SESSIONS_TRACKS_SESSION_ID + " WHERE " + Qualified.SESSIONS_TRACKS_TRACK_ID + "=" + Qualified.TRACKS_TRACK_ID + " AND " + Qualified.SESSIONS_SESSION_TYPE + " != \"" + Sessions.SESSION_TYPE_OFFICE_HOURS + "\")"; String TRACK_OFFICE_HOURS_COUNT = "(SELECT COUNT(" + Qualified.SESSIONS_TRACKS_SESSION_ID + ") FROM " + Tables.SESSIONS_TRACKS + " INNER JOIN " + Tables.SESSIONS + " ON " + Qualified.SESSIONS_SESSION_ID + "=" + Qualified.SESSIONS_TRACKS_SESSION_ID + " WHERE " + Qualified.SESSIONS_TRACKS_TRACK_ID + "=" + Qualified.TRACKS_TRACK_ID + " AND " + Qualified.SESSIONS_SESSION_TYPE + " = \"" + Sessions.SESSION_TYPE_OFFICE_HOURS + "\")"; String TRACK_SANDBOX_COUNT = "(SELECT COUNT(" + Qualified.SANDBOX_COMPANY_ID + ") FROM " + Tables.SANDBOX + " WHERE " + Qualified.SANDBOX_TRACK_ID + "=" + Qualified.TRACKS_TRACK_ID + ")"; String SESSIONS_SNIPPET = "snippet(" + Tables.SESSIONS_SEARCH + ",'{','}','\u2026')"; } /** * {@link ScheduleContract} fields that are fully qualified with a specific * parent {@link Tables}. Used when needed to work around SQL ambiguity. */ private interface Qualified { String SESSIONS_SESSION_ID = Tables.SESSIONS + "." + Sessions.SESSION_ID; String SESSIONS_SESSION_TYPE = Tables.SESSIONS+ "." + Sessions.SESSION_TYPE; String SESSIONS_BLOCK_ID = Tables.SESSIONS + "." + Sessions.BLOCK_ID; String SESSIONS_ROOM_ID = Tables.SESSIONS + "." + Sessions.ROOM_ID; String SESSIONS_TRACKS_SESSION_ID = Tables.SESSIONS_TRACKS + "." + SessionsTracks.SESSION_ID; String SESSIONS_TRACKS_TRACK_ID = Tables.SESSIONS_TRACKS + "." + SessionsTracks.TRACK_ID; String SESSIONS_SPEAKERS_SESSION_ID = Tables.SESSIONS_SPEAKERS + "." + SessionsSpeakers.SESSION_ID; String SESSIONS_SPEAKERS_SPEAKER_ID = Tables.SESSIONS_SPEAKERS + "." + SessionsSpeakers.SPEAKER_ID; String SANDBOX_COMPANY_ID = Tables.SANDBOX + "." + Sandbox.COMPANY_ID; String SANDBOX_TRACK_ID = Tables.SANDBOX + "." + Sandbox.TRACK_ID; String SESSIONS_STARRED = Tables.SESSIONS + "." + Sessions.SESSION_STARRED; String SESSIONS_TITLE = Tables.SESSIONS + "." + Sessions.SESSION_TITLE; String SESSIONS_HASHTAGS = Tables.SESSIONS + "." + Sessions.SESSION_HASHTAGS; String SESSIONS_URL = Tables.SESSIONS + "." + Sessions.SESSION_URL; String SESSIONS_LIVESTREAM_URL = Tables.SESSIONS + "." + Sessions.SESSION_LIVESTREAM_URL; String ROOMS_ROOM_NAME = Tables.ROOMS + "." + Rooms.ROOM_NAME; String ROOMS_ROOM_ID = Tables.ROOMS + "." + Rooms.ROOM_ID; String TRACKS_TRACK_ID = Tables.TRACKS + "." + Tracks.TRACK_ID; String BLOCKS_BLOCK_ID = Tables.BLOCKS + "." + Blocks.BLOCK_ID; } }