package com.kuxhausen.huemore.persistence; import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.database.DatabaseUtils; import android.database.MatrixCursor; import android.database.MergeCursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import com.kuxhausen.huemore.persistence.Definitions.AlarmColumns; import com.kuxhausen.huemore.persistence.Definitions.GroupBulbColumns; import com.kuxhausen.huemore.persistence.Definitions.GroupColumns; import com.kuxhausen.huemore.persistence.Definitions.MoodColumns; import com.kuxhausen.huemore.persistence.Definitions.NetBulbColumns; import com.kuxhausen.huemore.persistence.Definitions.NetConnectionColumns; import com.kuxhausen.huemore.persistence.Definitions.PlayingMood; import com.kuxhausen.huemore.persistence.migrations.DatabaseHelper; import com.kuxhausen.huemore.state.BulbState; import com.kuxhausen.huemore.state.Mood; import java.util.ArrayList; import java.util.HashMap; public class LampShadeProvider extends ContentProvider { DatabaseHelper mOpenHelper; /** * A UriMatcher instance */ private static final UriMatcher sUriMatcher; /** * Constants used by the Uri matcher to choose an action based on the pattern of the incoming URI */ private static final int GROUPS = 1, GROUPBULBS = 2, MOODS = 3, ALARMS = 4, NETBULBS = 5, NETCONNECTIONS = 6, PLAYINGMOOD = 7; /** * projection mapping between content provider value names and sql column names to enable * unambiguous joins in alarm query */ private static HashMap<String, String> sAlarmQueryProjectionMap; /** * A block that instantiates and sets static objects */ static { /* * Creates and initializes the URI matcher */ // Create a new instance sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); { sUriMatcher.addURI(Definitions.AUTHORITY, GroupColumns.PATH_GROUPS, GROUPS); sUriMatcher.addURI(Definitions.AUTHORITY, GroupBulbColumns.PATH_GROUPBULBS, GROUPBULBS); sUriMatcher.addURI(Definitions.AUTHORITY, MoodColumns.PATH_MOODS, MOODS); sUriMatcher.addURI(Definitions.AUTHORITY, AlarmColumns.PATH_ALARMS, ALARMS); sUriMatcher.addURI(Definitions.AUTHORITY, NetBulbColumns.PATH, NETBULBS); sUriMatcher.addURI(Definitions.AUTHORITY, NetConnectionColumns.PATH, NETCONNECTIONS); sUriMatcher.addURI(Definitions.AUTHORITY, PlayingMood.PATH, PLAYINGMOOD); sAlarmQueryProjectionMap = new HashMap<String, String>(); sAlarmQueryProjectionMap .put(AlarmColumns._ID, AlarmColumns.TABLE_NAME + "." + AlarmColumns._ID); sAlarmQueryProjectionMap.put(AlarmColumns.COL_GROUP_ID, AlarmColumns.COL_GROUP_ID); sAlarmQueryProjectionMap.put(GroupColumns.COL_GROUP_NAME, GroupColumns.COL_GROUP_NAME); sAlarmQueryProjectionMap.put(AlarmColumns.COL_MOOD_ID, AlarmColumns.COL_MOOD_ID); sAlarmQueryProjectionMap.put(MoodColumns.COL_MOOD_NAME, MoodColumns.COL_MOOD_NAME); sAlarmQueryProjectionMap.put(AlarmColumns.COL_BRIGHTNESS, AlarmColumns.COL_BRIGHTNESS); sAlarmQueryProjectionMap.put(AlarmColumns.COL_IS_ENABLED, AlarmColumns.COL_IS_ENABLED); sAlarmQueryProjectionMap.put(AlarmColumns.COL_REPEAT_DAYS, AlarmColumns.COL_REPEAT_DAYS); sAlarmQueryProjectionMap.put(AlarmColumns.COL_YEAR, AlarmColumns.COL_YEAR); sAlarmQueryProjectionMap.put(AlarmColumns.COL_MONTH, AlarmColumns.COL_MONTH); sAlarmQueryProjectionMap.put(AlarmColumns.COL_DAY_OF_MONTH, AlarmColumns.COL_DAY_OF_MONTH); sAlarmQueryProjectionMap.put(AlarmColumns.COL_HOUR_OF_DAY, AlarmColumns.COL_HOUR_OF_DAY); sAlarmQueryProjectionMap.put(AlarmColumns.COL_MINUTE, AlarmColumns.COL_MINUTE); } } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { ArrayList<Uri> toNotify = new ArrayList<Uri>(); String table = null; /** * Choose the projection and adjust the "where" clause based on URI pattern-matching. */ switch (sUriMatcher.match(uri)) { case PLAYINGMOOD: table = PlayingMood.TABLE_NAME; toNotify.add(PlayingMood.URI); break; case NETCONNECTIONS: table = NetConnectionColumns.TABLE_NAME; toNotify.add(NetConnectionColumns.URI); toNotify.add(NetBulbColumns.URI); toNotify.add(GroupColumns.URI); toNotify.add(GroupBulbColumns.URI); break; case NETBULBS: table = NetBulbColumns.TABLE_NAME; toNotify.add(NetBulbColumns.URI); break; case ALARMS: table = (AlarmColumns.TABLE_NAME); toNotify.add(AlarmColumns.ALARMS_URI); break; case GROUPS: table = (GroupColumns.TABLE_NAME); toNotify.add(GroupColumns.URI); toNotify.add(GroupBulbColumns.URI); break; case GROUPBULBS: table = (GroupBulbColumns.TABLE_NAME); toNotify.add(GroupBulbColumns.URI); toNotify.add(GroupColumns.URI); break; case MOODS: table = (Definitions.MoodColumns.TABLE_NAME); toNotify.add(Definitions.MoodColumns.MOODS_URI); break; default: // If the URI doesn't match any of the known patterns, throw an exception. throw new IllegalArgumentException("Unknown URI " + uri); } SQLiteDatabase db = mOpenHelper.getWritableDatabase(); int rowsAffected = db.delete(table, selection, selectionArgs); for (Uri me : toNotify) { this.getContext().getContentResolver().notifyChange(me, null); } return rowsAffected; } @Override public String getType(Uri uri) { // TODO Auto-generated method stub return null; } @Override public Uri insert(Uri uri, ContentValues values) { ArrayList<Uri> toNotify = new ArrayList<Uri>(); SQLiteDatabase db = mOpenHelper.getWritableDatabase(); // Constructs a new query builder and sets its table name SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); /** * Choose the projection and adjust the "where" clause based on URI pattern-matching. */ switch (sUriMatcher.match(uri)) { case PLAYINGMOOD: qb.setTables(PlayingMood.TABLE_NAME); toNotify.add(PlayingMood.URI); break; case NETCONNECTIONS: qb.setTables(NetConnectionColumns.TABLE_NAME); toNotify.add(NetConnectionColumns.URI); break; case NETBULBS: qb.setTables(NetBulbColumns.TABLE_NAME); toNotify.add(NetBulbColumns.URI); toNotify.add(GroupColumns.URI); toNotify.add(GroupBulbColumns.URI); // must notify the all mood that more bulbs exist break; case ALARMS: qb.setTables(AlarmColumns.TABLE_NAME); toNotify.add(AlarmColumns.ALARMS_URI); break; case GROUPS: qb.setTables(GroupColumns.TABLE_NAME); toNotify.add(GroupColumns.URI); toNotify.add(GroupBulbColumns.URI); break; case GROUPBULBS: qb.setTables(GroupBulbColumns.TABLE_NAME); toNotify.add(GroupBulbColumns.URI); toNotify.add(GroupColumns.URI); break; case MOODS: qb.setTables(Definitions.MoodColumns.TABLE_NAME); toNotify.add(Definitions.MoodColumns.MOODS_URI); break; default: // If the URI doesn't match any of the known patterns, throw an exception. throw new IllegalArgumentException("Unknown URI " + uri); } long insertId = db.insert(qb.getTables(), null, values); if (insertId == -1) { // insert failed // TODO handle better } if (sUriMatcher.match(uri) == NETBULBS) { //insert the NetBulb into the all mood String[] gColumns = {GroupColumns._ID}; String gWhere = GroupColumns.COL_GROUP_FLAGS + "=?"; String[] gWhereArgs = {"" + GroupColumns.FLAG_ALL}; Cursor gCursor = db.query(GroupColumns.TABLE_NAME, gColumns, gWhere, gWhereArgs, null, null, null); gCursor.moveToFirst(); long allGroupId = gCursor.getLong(0); long netBulbsCount = DatabaseUtils.queryNumEntries(db, NetBulbColumns.TABLE_NAME); ContentValues gbValues = new ContentValues(); gbValues.put(GroupBulbColumns.COL_GROUP_ID, allGroupId); gbValues.put(GroupBulbColumns.COL_BULB_PRECEDENCE, netBulbsCount - 1); gbValues.put(GroupBulbColumns.COL_NET_BULB_ID, insertId); db.insert(GroupBulbColumns.TABLE_NAME, null, gbValues); gCursor.close(); } for (Uri me : toNotify) { this.getContext().getContentResolver().notifyChange(me, null); } return ContentUris.withAppendedId(uri, insertId); } @Override public boolean onCreate() { // Creates a new helper object. Note that the database itself isn't opened until something tries // to access it, and it's only created if it doesn't already exist. mOpenHelper = new DatabaseHelper(getContext()); // Assumes that any failures will be reported by a thrown exception. return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // Opens the database object in "read" mode, since no writes need to be done. SQLiteDatabase db = mOpenHelper.getReadableDatabase(); // Constructs a new query builder and sets its table name SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); String groupBy = null; /** * Choose the projection and adjust the "where" clause based on URI pattern-matching. */ switch (sUriMatcher.match(uri)) { case PLAYINGMOOD: qb.setTables(PlayingMood.TABLE_NAME); groupBy = null; break; case NETCONNECTIONS: qb.setTables(NetConnectionColumns.TABLE_NAME); groupBy = null; break; case NETBULBS: qb.setTables(NetBulbColumns.TABLE_NAME); groupBy = null; break; case ALARMS: qb.setProjectionMap(sAlarmQueryProjectionMap); qb.setTables(AlarmColumns.TABLE_NAME + " JOIN " + GroupColumns.TABLE_NAME + " ON (" + AlarmColumns.COL_GROUP_ID + " = " + GroupColumns.TABLE_NAME + "." + GroupColumns._ID + ")" + " JOIN " + MoodColumns.TABLE_NAME + " ON (" + AlarmColumns.COL_MOOD_ID + " = " + MoodColumns.TABLE_NAME + "." + MoodColumns._ID + ")"); groupBy = null; if (sortOrder == null || sortOrder.equals("")) { sortOrder = AlarmColumns.COL_HOUR_OF_DAY + " ASC, " + AlarmColumns.COL_MINUTE + " ASC"; } break; case GROUPS: qb.setTables(GroupColumns.TABLE_NAME); groupBy = null; if (sortOrder == null || sortOrder.equals("")) { sortOrder = GroupColumns.COL_GROUP_PRIORITY + " DESC," + GroupColumns.COL_GROUP_LOWERCASE_NAME + " COLLATE UNICODE"; } break; case GROUPBULBS: qb.setTables(GroupBulbColumns.TABLE_NAME); groupBy = null; break; case MOODS: qb.setTables(MoodColumns.TABLE_NAME); groupBy = null; if (sortOrder == null || sortOrder.equals("")) { sortOrder = MoodColumns.COL_MOOD_PRIORITY + " DESC," + MoodColumns.COL_MOOD_LOWERCASE_NAME + " COLLATE UNICODE"; } break; default: // If the URI doesn't match any of the known patterns, throw an // exception. throw new IllegalArgumentException("Unknown URI " + uri); } /* * Performs the query. If no problems occur trying to read the database, then a Cursor object is * returned; otherwise, the cursor variable contains null. If no records were selected, then the * Cursor object is empty, and Cursor.getCount() returns 0. */ Cursor c2 = qb.query(db, projection, // The columns to return from the query selection, // The columns for the where clause selectionArgs, // The values for the where clause groupBy, // don't group the rows null, // don't filter by row groups sortOrder); Cursor[] cRay; if (sUriMatcher.match(uri) == MOODS && c2.getCount() < 1) { // If mood doesn't exist in db, return a blank mood BulbState resultState = new BulbState(); Mood m = new Mood.Builder(resultState).build(); String[] moodColumns = {MoodColumns.COL_MOOD_VALUE}; MatrixCursor mc = new MatrixCursor(moodColumns); Object[] tempRow = {HueUrlEncoder.encode(m)}; mc.addRow(tempRow); mc.setNotificationUri(getContext().getContentResolver(), uri); return mc; } else { Cursor[] tempC = {c2}; cRay = tempC; } MergeCursor c = new MergeCursor(cRay); // Tells the Cursor what URI to watch, so it knows when its source data // changes. apparently the merge cursor doesn't forward notifications, so notify individually // too! c.setNotificationUri(getContext().getContentResolver(), uri); c2.setNotificationUri(getContext().getContentResolver(), uri); return c; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { ArrayList<Uri> toNotify = new ArrayList<Uri>(); // Opens the database object in "write" mode. SQLiteDatabase db = mOpenHelper.getWritableDatabase(); int count; // Does the update based on the incoming URI pattern switch (sUriMatcher.match(uri)) { case MOODS: count = db.update(MoodColumns.TABLE_NAME, values, selection, selectionArgs); if (values.size() == 1 && values.containsKey(MoodColumns.COL_MOOD_PRIORITY)) { //If only the mood priority changed, //Don't notify because there's no animation for list reordering yet } else { toNotify.add(MoodColumns.MOODS_URI); } break; case NETCONNECTIONS: count = db.update(NetConnectionColumns.TABLE_NAME, values, selection, selectionArgs); toNotify.add(NetConnectionColumns.URI); break; case NETBULBS: count = db.update(NetBulbColumns.TABLE_NAME, values, selection, selectionArgs); if (values.size() == 1 && values.containsKey(NetBulbColumns.CURRENT_MAX_BRIGHTNESS)) { //If only the group max brightness changed, //Don't notify. This should be moved to a separate table at some point. } else { toNotify.add(NetBulbColumns.URI); } break; case GROUPS: count = db.update(GroupColumns.TABLE_NAME, values, selection, selectionArgs); toNotify.add(GroupColumns.URI); break; case GROUPBULBS: count = db.update(GroupBulbColumns.TABLE_NAME, values, selection, selectionArgs); toNotify.add(GroupBulbColumns.URI); break; case ALARMS: count = db.update(AlarmColumns.TABLE_NAME, values, selection, selectionArgs); toNotify.add(AlarmColumns.ALARMS_URI); break; default: // If the incoming pattern is invalid, throws an exception. throw new IllegalArgumentException("Unknown URI " + uri); } for (Uri me : toNotify) { this.getContext().getContentResolver().notifyChange(me, null); } // Returns the number of rows updated. return count; } }