package vandy.mooc.model.provider; import vandy.mooc.model.provider.WeatherContract.WeatherConditionsEntry; import vandy.mooc.model.provider.WeatherContract.WeatherValuesEntry; import android.content.ContentProvider; 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.SQLiteQueryBuilder; import android.net.Uri; import android.text.TextUtils; import android.util.Log; /** * Content Provider used to store information about weather data * returned from the Weather Service web service. */ public class WeatherProvider extends ContentProvider { /** * Logcat tag. */ private final String TAG = getClass().getCanonicalName(); /** * UriMatcher code for the Weather Values table. */ public static final int WEATHER_VALUES_ITEMS = 100; /** * UriMatcher code for a specific row in the Weather Values table. */ public static final int WEATHER_VALUES_ITEM = 110; /** * UriMatcher code for the Weather Conditions table. */ public static final int WEATHER_CONDITIONS_ITEMS = 200; /** * UriMatcher code for a specific row in the Weather Conditions * table. */ public static final int WEATHER_CONDITIONS_ITEM = 210; /** * UriMatcher code for getting an entire "WeatherData" object's * data from the database. This doesn't correspond to a specific * table; it corresponds to a Weather Values entry and all of its * associated Weather Conditions entries. */ public static final int ACCESS_ALL_DATA_FOR_LOCATION_ITEM = 300; /** * UriMatcher that is used to demultiplex the incoming URIs into * requests. */ public static final UriMatcher sUriMatcher = buildUriMatcher(); /** * Build the UriMatcher for this Content Provider. */ public static UriMatcher buildUriMatcher() { // Add default 'no match' result to matcher. final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); // Initialize the matcher with the URIs used to access each // table. matcher.addURI(WeatherContract.AUTHORITY, WeatherContract.WeatherValuesEntry.WEATHER_VALUES_TABLE_NAME, WEATHER_VALUES_ITEMS); matcher.addURI(WeatherContract.AUTHORITY, WeatherContract.WeatherValuesEntry.WEATHER_VALUES_TABLE_NAME + "/#", WEATHER_VALUES_ITEM); matcher.addURI(WeatherContract.AUTHORITY, WeatherContract.WeatherConditionsEntry.WEATHER_CONDITIONS_TABLE_NAME, WEATHER_CONDITIONS_ITEMS); matcher.addURI(WeatherContract.AUTHORITY, WeatherContract.WeatherConditionsEntry.WEATHER_CONDITIONS_TABLE_NAME + "/#", WEATHER_CONDITIONS_ITEM); matcher.addURI(WeatherContract.AUTHORITY, WeatherContract.ACCESS_ALL_DATA_FOR_LOCATION_PATH, ACCESS_ALL_DATA_FOR_LOCATION_ITEM); return matcher; } /* * Constants referencing the Contract class. Used for convenience * to avoid having to retype long constant names. */ /** * Constant for the Weather Values table's name. */ private static final String WEATHER_VALUES_TABLE_NAME = WeatherContract.WeatherValuesEntry.WEATHER_VALUES_TABLE_NAME; /** * Constant for the Weather Conditions table's name. */ private static final String WEATHER_CONDITIONS_TABLE_NAME = WeatherContract.WeatherConditionsEntry.WEATHER_CONDITIONS_TABLE_NAME; /** * The database helper that is used to manage the providers * database. */ private WeatherDatabaseHelper mDatabaseHelper; /** * Hook method called when the provider is created. */ @Override public boolean onCreate() { mDatabaseHelper = new WeatherDatabaseHelper(getContext()); return true; } /** * Helper method that appends a given key id to the end of the * WHERE statement parameter. */ private static String addKeyIdCheckToWhereStatement(String whereStatement, long id) { String newWhereStatement; if (TextUtils.isEmpty(whereStatement)) newWhereStatement = ""; else newWhereStatement = whereStatement + " AND "; return newWhereStatement + " _id = " + "'" + id + "'"; } /** * Get a Cursor containing all data for a selected location. It * joins the Weather Values and Weather Conditions tables. It * will have a row for each Weather object corresponding to the * location, with the Weather Values columns repeated. */ private Cursor getAllLocationsData(String locationKey, Uri uri) { /** * Constant defining the FROM and WHERE clauses for a * statement working on all the data for a single Weather * Values "object". This WHERE statement is used to join both * the Weather Values and Weather Conditions tables over a * specific location. */ final String FROM_WHERE_STATEMENT_ALL_LOCATION_DATA = "SELECT * FROM " + WEATHER_VALUES_TABLE_NAME + ", " + WEATHER_CONDITIONS_TABLE_NAME + " WHERE " + WEATHER_VALUES_TABLE_NAME + "." + WeatherContract.WeatherValuesEntry.COLUMN_LOCATION_KEY + " = ? AND " + WEATHER_VALUES_TABLE_NAME + "." + WeatherContract.WeatherValuesEntry.COLUMN_LOCATION_KEY + " = " + WEATHER_CONDITIONS_TABLE_NAME + "." + WeatherContract.WeatherConditionsEntry.COLUMN_LOCATION_KEY; // Retreive the database from the helper final SQLiteDatabase db = mDatabaseHelper.getReadableDatabase(); // Formulate the query statement. final String selectQuery = FROM_WHERE_STATEMENT_ALL_LOCATION_DATA; Log.v(TAG, selectQuery); // Query the SQLite database using the all-locations Uri, // which returns a Cursor joining the Weather Values and // Conditions table entries for one WeatherData object for the // target location. Cursor result = db.rawQuery(selectQuery, new String[] { locationKey }); // Set the cursor's notification to enable CursorAdapters // to listen for changes. result.setNotificationUri(getContext().getContentResolver(), uri); return result; } /** * Method called to handle query requests from client * applications. */ @Override public Cursor query(Uri uri, String[] projection, String whereStatement, String[] whereStatementArgs, String sortOrder) { // Create a SQLite query builder that will be modified based // on the Uri passed. final SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); // Use the passed Uri to determine how to build the // query. This will determine the table that the query will // act on and possibly add row qualifications to the WHERE // clause. switch (sUriMatcher.match(uri)) { case WEATHER_VALUES_ITEMS: queryBuilder.setTables(WEATHER_VALUES_TABLE_NAME); break; case WEATHER_VALUES_ITEM: queryBuilder.setTables(WEATHER_VALUES_TABLE_NAME); whereStatement = addKeyIdCheckToWhereStatement(whereStatement, ContentUris.parseId(uri)); break; case WEATHER_CONDITIONS_ITEMS: queryBuilder.setTables(WEATHER_CONDITIONS_TABLE_NAME); break; case WEATHER_CONDITIONS_ITEM: queryBuilder.setTables(WEATHER_CONDITIONS_TABLE_NAME); whereStatement = addKeyIdCheckToWhereStatement(whereStatement, ContentUris.parseId(uri)); break; case ACCESS_ALL_DATA_FOR_LOCATION_ITEM: // This is a special Uri that is querying for an entire // WeatherData object. return getAllLocationsData(whereStatementArgs[0], uri); default: throw new IllegalArgumentException("Unknown URI " + uri); } // Once the query builder has been initialized based on the // provided Uri, use it to query the database. final Cursor cursor = queryBuilder.query(mDatabaseHelper.getReadableDatabase(), projection, whereStatement, whereStatementArgs, null, // GROUP BY (not used) null, // HAVING (not used) sortOrder); // Register to watch a content URI for changes. cursor.setNotificationUri(getContext().getContentResolver(), uri); return cursor; } /** * Method called to handle type requests from client applications. * It returns the MIME type of the data associated with each URI. */ @Override public String getType(Uri uri) { // Use the passed Uri to determine what data is being asked // for and return the appropriate MIME type switch (sUriMatcher.match(uri)) { case WEATHER_VALUES_ITEMS: return WeatherValuesEntry.WEATHER_VALUES_ITEM; case WEATHER_VALUES_ITEM: return WeatherValuesEntry.WEATHER_VALUES_ITEM; case WEATHER_CONDITIONS_ITEMS: return WeatherConditionsEntry.WEATHER_CONDITIONS_ITEMS; case WEATHER_CONDITIONS_ITEM: return WeatherConditionsEntry.WEATHER_CONDITIONS_ITEM; case ACCESS_ALL_DATA_FOR_LOCATION_ITEM: return WeatherContract.ACCESS_ALL_DATA_FOR_LOCATION; default: throw new IllegalArgumentException("Unknown URI " + uri); } } /** * Method called to handle insert requests from client * applications. */ @Override public Uri insert(Uri uri, ContentValues values) { // The table to perform the insert on. String table; // The Uri containing the inserted row's id that is returned // to the caller. Uri resultUri; // Determine the base Uri to return and the table to insert on // using the UriMatcher. switch (sUriMatcher.match(uri)) { case WEATHER_VALUES_ITEMS: table = WEATHER_VALUES_TABLE_NAME; resultUri = WeatherValuesEntry.WEATHER_VALUES_CONTENT_URI; break; case WEATHER_CONDITIONS_ITEMS: table = WEATHER_CONDITIONS_TABLE_NAME; resultUri = WeatherConditionsEntry.WEATHER_CONDITIONS_CONTENT_URI; break; default: throw new IllegalArgumentException("Unknown URI " + uri); } // Insert the data into the correct table. final long insertRow = mDatabaseHelper.getWritableDatabase().insert (table, null, values); // Check to ensure that the insertion worked. if (insertRow > 0) { // Create the result URI. Uri newUri = ContentUris.withAppendedId(resultUri, insertRow); // Register to watch a content URI for changes. getContext().getContentResolver().notifyChange(newUri, null); // Register a change to the all-data uri getContext().getContentResolver() .notifyChange (WeatherContract.ACCESS_ALL_DATA_FOR_LOCATION_URI, null); return newUri; } else throw new SQLException("Fail to add a new record into " + uri); } /** * Method that handles bulk insert requests. */ @Override public int bulkInsert(Uri uri, ContentValues[] values) { // Fetch the db from the helper. final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase(); String dbName; // Match the Uri against the table's uris to determine the // table in which table to insert the values. switch(sUriMatcher.match(uri)) { case WEATHER_VALUES_ITEMS: dbName = WeatherValuesEntry.WEATHER_VALUES_TABLE_NAME; break; case WEATHER_CONDITIONS_ITEMS: dbName = WeatherConditionsEntry.WEATHER_CONDITIONS_TABLE_NAME; break; default: throw new IllegalArgumentException("Unknown URI " + uri); } // Insert the values into the table in one transaction by // beginning a transaction in EXCLUSIVE mode. db.beginTransaction(); int returnCount = 0; try { for (ContentValues value : values) { final long id = db.insert(dbName, null, value); if (id != -1) returnCount++; } // Marks the current transaction as successful. db.setTransactionSuccessful(); } finally { // End the transaction db.endTransaction(); } // Notifies registered observers that rows were updated and // attempt to sync changes to the network. getContext().getContentResolver().notifyChange(uri, null); // Register a change to the all-data URI getContext().getContentResolver() .notifyChange (WeatherContract.ACCESS_ALL_DATA_FOR_LOCATION_URI, null); return returnCount; } /** * Method called to handle update requests from client * applications. */ @Override public int update(Uri uri, ContentValues values, String whereStatement, String[] whereStatementArgs) { // Number of rows updated. int rowsUpdated; final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase(); // Update the appropriate rows. If the URI includes a // specific row to update, add that row to the where // statement. switch (sUriMatcher.match(uri)) { case WEATHER_VALUES_ITEMS: rowsUpdated = db.update(WEATHER_VALUES_TABLE_NAME, values, whereStatement, whereStatementArgs); break; case WEATHER_VALUES_ITEM: rowsUpdated = db.update(WEATHER_VALUES_TABLE_NAME, values, addKeyIdCheckToWhereStatement (whereStatement, ContentUris.parseId(uri)), whereStatementArgs); break; case WEATHER_CONDITIONS_ITEMS: rowsUpdated = db.update(WEATHER_CONDITIONS_TABLE_NAME, values, whereStatement, whereStatementArgs); break; case WEATHER_CONDITIONS_ITEM: rowsUpdated = db.update(WEATHER_CONDITIONS_TABLE_NAME, values, addKeyIdCheckToWhereStatement (whereStatement, ContentUris.parseId(uri)), whereStatementArgs); break; default: throw new IllegalArgumentException("Unknown URI " + uri); } // Register to watch a content URI for changes. getContext().getContentResolver().notifyChange(uri, null); // Register a change to the all-data uri. getContext().getContentResolver() .notifyChange (WeatherContract.ACCESS_ALL_DATA_FOR_LOCATION_URI, null); return rowsUpdated; } /** * Method called to handle delete requests from client * applications. */ @Override public int delete(Uri uri, String whereStatement, String[] whereStatementArgs) { // Number of rows deleted. int rowsDeleted; final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase(); // Delete the appropriate rows based on the Uri. If the URI // includes a specific row to delete, add that row to the // WHERE statement. switch (sUriMatcher.match(uri)) { case WEATHER_VALUES_ITEMS: rowsDeleted = db.delete(WEATHER_VALUES_TABLE_NAME, whereStatement, whereStatementArgs); break; case WEATHER_VALUES_ITEM: rowsDeleted = db.delete(WEATHER_VALUES_TABLE_NAME, addKeyIdCheckToWhereStatement (whereStatement, ContentUris.parseId(uri)), whereStatementArgs); break; case WEATHER_CONDITIONS_ITEMS: rowsDeleted = db.delete(WEATHER_CONDITIONS_TABLE_NAME, whereStatement, whereStatementArgs); break; case WEATHER_CONDITIONS_ITEM: rowsDeleted = db.delete(WEATHER_CONDITIONS_TABLE_NAME, addKeyIdCheckToWhereStatement (whereStatement, ContentUris.parseId(uri)), whereStatementArgs); break; default: throw new IllegalArgumentException("Unknown URI " + uri); } // Register to watch a content URI for changes. getContext().getContentResolver().notifyChange(uri, null); // Register a change to the all-data uri. getContext().getContentResolver() .notifyChange (WeatherContract.ACCESS_ALL_DATA_FOR_LOCATION_URI, null); return rowsDeleted; } }