/*
* Copyright (C) 2007-2011 OpenIntents.org
*
* 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 org.openintents.shopping.provider;
import android.content.*;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.MatrixCursor;
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;
import java.util.HashMap;
import org.openintents.intents.ProviderIntents;
import org.openintents.intents.ProviderUtils;
import org.openintents.shopping.LogConstants;
import org.openintents.shopping.R;
import org.openintents.shopping.library.provider.ShoppingContract;
import org.openintents.shopping.library.provider.ShoppingContract.*;
import org.openintents.shopping.ui.PreferenceActivity;
import org.openintents.shopping.ui.ShoppingActivity;
/**
* Provides access to a database of shopping items and shopping lists.
* <p/>
* ShoppingProvider maintains the following tables: * items: items you want to
* buy * lists: shopping lists ("My shopping list", "Bob's shopping list") *
* contains: which item/list/(recipe) is contained in which shopping list. *
* stores: * itemstores: (which store carries which item)
*/
public class ShoppingProvider extends ContentProvider {
private ShoppingDatabase mOpenHelper;
protected static final String TAG = "ShoppingProvider";
private static final boolean debug = false || LogConstants.debug;
private static HashMap<String, String> ITEMS_PROJECTION_MAP;
private static HashMap<String, String> LISTS_PROJECTION_MAP;
private static HashMap<String, String> CONTAINS_PROJECTION_MAP;
private static HashMap<String, String> CONTAINS_FULL_PROJECTION_MAP;
private static HashMap<String, String> CONTAINS_FULL_CHEAPEST_PROJECTION_MAP;
private static HashMap<String, String> CONTAINS_FULL_STORE_PROJECTION_MAP;
private static HashMap<String, String> STORES_PROJECTION_MAP;
private static HashMap<String, String> ITEMSTORES_PROJECTION_MAP;
private static HashMap<String, String> NOTES_PROJECTION_MAP;
private static HashMap<String, String> UNITS_PROJECTION_MAP;
private static HashMap<String, String> SUBTOTALS_PROJECTION_MAP;
// Basic tables
private static final int ITEMS = 1;
private static final int ITEM_ID = 2;
private static final int LISTS = 3;
private static final int LIST_ID = 4;
private static final int CONTAINS = 5;
private static final int CONTAINS_ID = 6;
private static final int STORES = 7;
private static final int STORES_ID = 8;
private static final int STORES_LISTID = 9;
private static final int ITEMSTORES = 10;
private static final int ITEMSTORES_ID = 11;
private static final int NOTES = 12;
private static final int NOTE_ID = 13;
private static final int UNITS = 14;
private static final int UNITS_ID = 15;
private static final int PREFS = 16;
private static final int ITEMSTORES_ITEMID = 17;
private static final int SUBTOTALS = 18;
private static final int SUBTOTALS_LISTID = 19;
private static final int CONTAINS_FULL_LISTID = 20;
// Derived tables
private static final int CONTAINS_FULL = 101; // combined with items and
// lists
private static final int CONTAINS_FULL_ID = 102;
private static final int ACTIVELIST = 103;
// duplicate specified contains record and its item, return ids
private static final int CONTAINS_COPYOFID = 104;
private static final int TAGS_LISTID = 105;
private static final UriMatcher URL_MATCHER;
@Override
public boolean onCreate() {
mOpenHelper = new ShoppingDatabase(getContext());
return true;
}
public static String escapeSQLChars(String trapped) {
/*
In order for this method to work properly, the query using the result must use '`' as its
Escape character.
*/
return trapped.replaceAll("'", "''").replaceAll("`", "``").replaceAll("%", "`%").replaceAll("_","`_");
}
@Override
public Cursor query(Uri url, String[] projection, String selection,
String[] selectionArgs, String sort) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
long list_id = -1;
if (debug) {
Log.d(TAG, "Query for URL: " + url);
}
String defaultOrderBy = null;
String groupBy = null;
switch (URL_MATCHER.match(url)) {
case ITEMS:
qb.setTables("items");
qb.setProjectionMap(ITEMS_PROJECTION_MAP);
defaultOrderBy = Items.DEFAULT_SORT_ORDER;
break;
case ITEM_ID:
qb.setTables("items");
qb.appendWhere("_id=" + url.getPathSegments().get(1));
break;
case LISTS:
qb.setTables("lists");
qb.setProjectionMap(LISTS_PROJECTION_MAP);
defaultOrderBy = Lists.DEFAULT_SORT_ORDER;
break;
case LIST_ID:
qb.setTables("lists");
qb.appendWhere("_id=" + url.getPathSegments().get(1));
break;
case CONTAINS:
qb.setTables("contains");
qb.setProjectionMap(CONTAINS_PROJECTION_MAP);
defaultOrderBy = Contains.DEFAULT_SORT_ORDER;
break;
case CONTAINS_ID:
qb.setTables("contains");
qb.appendWhere("_id=" + url.getPathSegments().get(1));
break;
case CONTAINS_FULL:
boolean inSearchMode = appIsInSearchMode();
// all callers pass list id as selection_args[0]. perhaps not so
// nice to depend on that, but... need to choose the projection map
// based on the list's store filter.
if (!inSearchMode
&& PreferenceActivity.getUsingFiltersFromPrefs(getContext())
&& listUsesStoreFilter(selectionArgs[0])) {
// actually there are two ways we could do the query when
// filtering by stores. perhaps
// we should offer both. for now choose the first one...
if (true) {
// show only items which have corresponding records in
// itemstores
qb.setTables("contains, items, lists, itemstores");
qb.setProjectionMap(CONTAINS_FULL_STORE_PROJECTION_MAP);
qb.appendWhere("contains.item_id = items._id AND "
+ "contains.list_id = lists._id AND "
+ "items._id = itemstores.item_id AND "
+ "lists.store_filter = itemstores.store_id");
} else {
// this query is not quite right, but the idea is
// show all items, but only show prices from the selected
// store.
qb.setTables("contains, items, lists left outer join itemstores on (items._id = itemstores.item_id)");
qb.setProjectionMap(CONTAINS_FULL_STORE_PROJECTION_MAP);
qb.appendWhere("contains.item_id = items._id AND "
+ "contains.list_id = lists._id AND "
+ "items._id = itemstores.item_id AND "
+ "lists.store_filter = itemstores.store_id");
}
} else if (PreferenceActivity
.getUsingPerStorePricesFromPrefs(getContext())) {
qb.setTables("contains, items, lists left outer join itemstores on (items._id = itemstores.item_id)");
qb.setProjectionMap(CONTAINS_FULL_CHEAPEST_PROJECTION_MAP);
qb.appendWhere("contains.item_id = items._id AND "
+ "contains.list_id = lists._id");
groupBy = "items._id";
} else {
qb.setTables("contains, items, lists");
qb.setProjectionMap(CONTAINS_FULL_PROJECTION_MAP);
qb.appendWhere("contains.item_id = items._id AND "
+ "contains.list_id = lists._id");
}
defaultOrderBy = ContainsFull.DEFAULT_SORT_ORDER;
String tagFilter = getListTagsFilter(selectionArgs[0]);
if (!inSearchMode && !TextUtils.isEmpty(tagFilter)) {
qb.appendWhere(" AND items.tags like '%" + escapeSQLChars(tagFilter) + "%' ESCAPE '`'");
}
break;
case CONTAINS_FULL_ID:
qb.setTables("contains, items, lists");
qb.appendWhere("_id=" + url.getPathSegments().get(1));
qb.appendWhere("contains.item_id = items._id AND "
+ "contains.list_id = lists._id");
break;
case CONTAINS_FULL_LISTID:
list_id = Long.parseLong(url.getPathSegments().get(2));
qb.setTables("contains, items, lists");
qb.appendWhere("contains.item_id = items._id AND " +
"contains.list_id = lists._id AND " +
"lists._id = " + list_id);
break;
case STORES:
qb.setTables("stores");
qb.setProjectionMap(STORES_PROJECTION_MAP);
break;
case STORES_ID:
qb.setTables("stores");
qb.appendWhere("_id=" + url.getPathSegments().get(1));
break;
case STORES_LISTID:
qb.setTables("stores");
qb.setProjectionMap(STORES_PROJECTION_MAP);
qb.appendWhere("list_id=" + url.getPathSegments().get(1));
break;
case TAGS_LISTID:
// this is for querying tags regardless of filters.
// might want to restrict the projection map.
qb.setTables("contains, items, lists");
qb.setProjectionMap(CONTAINS_FULL_PROJECTION_MAP);
qb.appendWhere("contains.item_id = items._id AND "
+ "contains.list_id = lists._id AND " + "contains.list_id="
+ url.getPathSegments().get(1));
groupBy = "items.tags";
break;
case ITEMSTORES:
qb.setTables("itemstores, items, stores");
qb.setProjectionMap(ITEMSTORES_PROJECTION_MAP);
qb.appendWhere("itemstores.item_id = items._id AND itemstores.store_id = stores._id");
break;
case ITEMSTORES_ID:
qb.setTables("itemstores, items, stores");
qb.appendWhere("_id=" + url.getPathSegments().get(1));
qb.appendWhere("itemstores.item_id = items._id AND itemstores.store_id = stores._id");
break;
case ITEMSTORES_ITEMID:
// path segment 1 is "item", path segment 2 is item id, path segment
// 3 is list id.
qb.setTables("stores left outer join itemstores on (stores._id = itemstores.store_id and "
+ "itemstores.item_id = "
+ url.getPathSegments().get(2)
+ ")");
qb.appendWhere("stores.list_id = " + url.getPathSegments().get(3));
break;
case NOTES:
qb.setTables("items");
qb.setProjectionMap(NOTES_PROJECTION_MAP);
break;
case NOTE_ID:
qb.setTables("items");
qb.setProjectionMap(NOTES_PROJECTION_MAP);
qb.appendWhere("_id=" + url.getPathSegments().get(1));
break;
case UNITS:
qb.setTables("units");
qb.setProjectionMap(UNITS_PROJECTION_MAP);
break;
case UNITS_ID:
qb.setTables("units");
qb.setProjectionMap(UNITS_PROJECTION_MAP);
qb.appendWhere("_id=" + url.getPathSegments().get(1));
break;
case ACTIVELIST:
MatrixCursor m = new MatrixCursor(projection);
// assumes only one projection will ever be used,
// asking only for the id of the active list.
SharedPreferences sp = getContext().getSharedPreferences(
"org.openintents.shopping_preferences",
Context.MODE_PRIVATE);
list_id = sp.getInt("lastused", 1);
m.addRow(new Object[]{Long.toString(list_id)});
return m;
case PREFS:
m = new MatrixCursor(projection);
// assumes only one projection will ever be used,
// asking only for the id of the active list.
String sortOrder = PreferenceActivity.getSortOrderFromPrefs(
getContext(), ShoppingActivity.MODE_IN_SHOP);
m.addRow(new Object[]{sortOrder});
return m;
case SUBTOTALS_LISTID:
list_id = Long.parseLong(url.getPathSegments().get(1));
// FALLTHROUGH
case SUBTOTALS:
if (list_id == -1) {
// this gets the wrong answer if user has switched lists in this
// session.
sp = getContext().getSharedPreferences(
"org.openintents.shopping_preferences",
Context.MODE_PRIVATE);
list_id = sp.getInt("lastused", 1);
}
qb.setProjectionMap(SUBTOTALS_PROJECTION_MAP);
groupBy = "priority, status";
if (PreferenceActivity
.getUsingPerStorePricesFromPrefs(getContext())) {
// status added to "group by" to cover the case where there are
// no store prices
// for any checked items. still need to count them separately so
// Clean List
// can be ungreyed.
qb.setTables("(SELECT (min(itemstores.price) * case when ((contains.quantity is null) or (length(contains.quantity) = 0)) then 1 else contains.quantity end) as qty_price, "
+ "contains.status as status, contains.priority as priority FROM contains, items left outer join itemstores on (items._id = itemstores.item_id) "
+ "WHERE (contains.item_id = items._id AND contains.list_id = "
+ list_id
+ " ) AND contains.status != 3 GROUP BY itemstores.item_id, status) ");
} else {
qb.setTables("(SELECT (items.price * case when ((contains.quantity is null) or (length(contains.quantity) = 0)) then 1 else contains.quantity end) as qty_price, "
+ "contains.status as status, contains.priority as priority FROM contains, items "
+ "WHERE (contains.item_id = items._id AND contains.list_id = "
+ list_id + " ) AND contains.status != 3) ");
}
break;
case CONTAINS_COPYOFID:
long oldContainsId = Long.parseLong(url.getPathSegments().get(2));
return copyItemAndContains(projection, oldContainsId);
default:
throw new IllegalArgumentException("Unknown URL " + url);
}
// If no sort order is specified use the default
String orderBy;
if (TextUtils.isEmpty(sort)) {
orderBy = defaultOrderBy;
} else {
orderBy = sort;
}
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
if (debug) {
String qs = qb.buildQuery(projection, selection, null, groupBy,
null, orderBy, null);
Log.d(TAG, "Query : " + qs);
}
Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy,
null, orderBy);
c.setNotificationUri(getContext().getContentResolver(), url);
return c;
}
private boolean listUsesStoreFilter(String listId) {
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables("lists");
qb.appendWhere("_id=" + listId);
Cursor c = qb.query(db, new String[]{Lists.STORE_FILTER}, null,
null, null, null, null);
if (c.getCount() != 1) {
return false;
}
c.moveToFirst();
long storeId = c.getLong(0);
c.deactivate();
c.close();
return (storeId != -1);
}
private String getListTagsFilter(String listId) {
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables("lists");
qb.appendWhere("_id=" + listId);
Cursor c = qb.query(db, new String[]{Lists.TAGS_FILTER}, null, null,
null, null, null);
if (c.getCount() != 1) {
return null;
}
c.moveToFirst();
String tag = c.getString(0);
c.deactivate();
c.close();
return (tag);
}
private boolean appIsInSearchMode() {
SharedPreferences sp = getContext().getSharedPreferences(
"org.openintents.shopping_preferences",
Context.MODE_PRIVATE);
return sp.getBoolean("_searching", false);
}
// caller wants us to copy the item and the contains record.
// only supported projection is item_id, contains_id of the copy.
private Cursor copyItemAndContains(String[] projection, long oldContainsId) {
long oldItemId, containsCopyId, itemCopyId;
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
// find the item id from the contains record
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables("contains");
qb.appendWhere("_id=" + oldContainsId);
Cursor c = qb.query(db, new String[]{Contains.ITEM_ID}, null, null,
null, null, null);
if (c.getCount() != 1) {
return null;
}
c.moveToFirst();
oldItemId = c.getLong(0);
c.deactivate();
c.close();
// read the item
qb = new SQLiteQueryBuilder();
qb.setTables("items");
qb.appendWhere("_id=" + oldItemId);
c = qb.query(db, Items.PROJECTION_TO_COPY, null, null, null, null, null);
if (c.getCount() != 1) {
return null;
}
c.moveToFirst();
ContentValues itemValues = new ContentValues();
DatabaseUtils.cursorRowToContentValues(c, itemValues);
c.deactivate();
c.close();
// read the contains record
qb = new SQLiteQueryBuilder();
qb.setTables("contains");
qb.appendWhere("_id=" + oldContainsId);
c = qb.query(db, Contains.PROJECTION_TO_COPY, null, null, null, null,
null);
if (c.getCount() != 1) {
return null;
}
c.moveToFirst();
ContentValues containsValues = new ContentValues();
DatabaseUtils.cursorRowToContentValues(c, containsValues);
c.deactivate();
c.close();
// insert the item copy
validateItemValues(itemValues);
itemCopyId = db.insert("items", "items", itemValues);
// insert the contains record copy
containsValues.put(Contains.ITEM_ID, itemCopyId);
validateContainsValues(containsValues);
containsCopyId = db.insert("contains", "contains", containsValues);
// not sure, should we also copy ItemStores records?
MatrixCursor m = new MatrixCursor(projection);
m.addRow(new Object[]{Long.toString(itemCopyId),
Long.toString(containsCopyId)});
return m;
}
@Override
public Uri insert(Uri url, ContentValues initialValues) {
ContentValues values;
if (initialValues != null) {
values = new ContentValues(initialValues);
} else {
values = new ContentValues();
}
// insert is supported for items or lists
switch (URL_MATCHER.match(url)) {
case ITEMS:
case NOTES:
return insertItem(url, values);
case LISTS:
return insertList(url, values);
case CONTAINS:
return insertContains(url, values);
case CONTAINS_FULL:
throw new IllegalArgumentException("Insert not supported for "
+ url + ", use CONTAINS instead of CONTAINS_FULL.");
case STORES:
return insertStore(url, values);
case ITEMSTORES:
return insertItemStore(url, values);
case UNITS:
return insertUnits(url, values);
default:
throw new IllegalArgumentException("Unknown URL " + url);
}
}
private Uri insertItem(Uri url, ContentValues values) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
long rowID;
validateItemValues(values);
// TODO: Here we should check, whether item exists already.
// (see TagsProvider)
// insert the item.
rowID = db.insert("items", "items", values);
if (rowID > 0) {
Uri uri = ContentUris.withAppendedId(Items.CONTENT_URI, rowID);
getContext().getContentResolver().notifyChange(uri, null);
Intent intent = new Intent(ProviderIntents.ACTION_INSERTED);
intent.setData(uri);
getContext().sendBroadcast(intent);
return uri;
}
// If everything works, we should not reach the following line:
throw new SQLException("Failed to insert row into " + url);
}
private void validateItemValues(ContentValues values) {
Long now = Long.valueOf(System.currentTimeMillis());
// Make sure that the fields are all set
if (!values.containsKey(Items.NAME)) {
Resources r = getContext().getResources();
values.put(Items.NAME, r.getString(R.string.new_item));
}
if (!values.containsKey(Items.IMAGE)) {
values.put(Items.IMAGE, "");
}
if (!values.containsKey(Items.CREATED_DATE)) {
values.put(Items.CREATED_DATE, now);
}
if (!values.containsKey(Items.MODIFIED_DATE)) {
values.put(Items.MODIFIED_DATE, now);
}
if (!values.containsKey(Items.ACCESSED_DATE)) {
values.put(Items.ACCESSED_DATE, now);
}
}
private Uri insertList(Uri url, ContentValues values) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
long rowID;
Long now = Long.valueOf(System.currentTimeMillis());
Resources r = Resources.getSystem();
// Make sure that the fields are all set
if (!values.containsKey(Lists.NAME)) {
values.put(Lists.NAME, r.getString(R.string.new_list));
}
if (!values.containsKey(Lists.IMAGE)) {
values.put(Lists.IMAGE, "");
}
if (!values.containsKey(Lists.CREATED_DATE)) {
values.put(Lists.CREATED_DATE, now);
}
if (!values.containsKey(Lists.MODIFIED_DATE)) {
values.put(Lists.MODIFIED_DATE, now);
}
if (!values.containsKey(Lists.ACCESSED_DATE)) {
values.put(Lists.ACCESSED_DATE, now);
}
if (!values.containsKey(Lists.SHARE_CONTACTS)) {
values.put(Lists.SHARE_CONTACTS, "");
}
if (!values.containsKey(Lists.SKIN_BACKGROUND)) {
values.put(Lists.SKIN_BACKGROUND, "");
}
if (!values.containsKey(Lists.SKIN_FONT)) {
values.put(Lists.SKIN_FONT, "");
}
if (!values.containsKey(Lists.SKIN_COLOR)) {
values.put(Lists.SKIN_COLOR, 0);
}
if (!values.containsKey(Lists.SKIN_COLOR_STRIKETHROUGH)) {
values.put(Lists.SKIN_COLOR_STRIKETHROUGH, 0xFF006600);
}
// TODO: Here we should check, whether item exists already.
// (see TagsProvider)
// insert the tag.
rowID = db.insert("lists", "lists", values);
if (rowID > 0) {
Uri uri = ContentUris.withAppendedId(Lists.CONTENT_URI, rowID);
getContext().getContentResolver().notifyChange(uri, null);
Intent intent = new Intent(ProviderIntents.ACTION_INSERTED);
intent.setData(uri);
getContext().sendBroadcast(intent);
return uri;
}
// If everything works, we should not reach the following line:
throw new SQLException("Failed to insert row into " + url);
}
private Uri insertContains(Uri url, ContentValues values) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
// Make sure that the fields are all set
if (!(values.containsKey(Contains.ITEM_ID) && values
.containsKey(Contains.LIST_ID))) {
// At least these values should exist.
throw new SQLException("Failed to insert row into " + url
+ ": ITEM_ID and LIST_ID must be given.");
}
// TODO: Check here that ITEM_ID and LIST_ID
// actually exist in the tables.
if (!values.containsKey(Contains.STATUS)) {
values.put(Contains.STATUS, Status.WANT_TO_BUY);
} else {
// Check here that STATUS is valid.
long s = values.getAsInteger(Contains.STATUS);
if (!Status.isValid(s)) {
throw new SQLException("Failed to insert row into " + url
+ ": Status " + s + " is not valid.");
}
}
validateContainsValues(values);
// TODO: Here we should check, whether item exists already.
// (see TagsProvider)
// insert the item.
long rowId = db.insert("contains", "contains", values);
if (rowId > 0) {
Uri uri = ContentUris.withAppendedId(Contains.CONTENT_URI, rowId);
getContext().getContentResolver().notifyChange(uri, null);
Intent intent = new Intent(ProviderIntents.ACTION_INSERTED);
intent.setData(uri);
getContext().sendBroadcast(intent);
return uri;
}
// If everything works, we should not reach the following line:
throw new SQLException("Failed to insert row into " + url);
}
private void validateContainsValues(ContentValues values) {
Long now = Long.valueOf(System.currentTimeMillis());
if (!values.containsKey(Contains.CREATED_DATE)) {
values.put(Contains.CREATED_DATE, now);
}
if (!values.containsKey(Contains.MODIFIED_DATE)) {
values.put(Contains.MODIFIED_DATE, now);
}
if (!values.containsKey(Contains.ACCESSED_DATE)) {
values.put(Contains.ACCESSED_DATE, now);
}
if (!values.containsKey(Contains.SHARE_CREATED_BY)) {
values.put(Contains.SHARE_CREATED_BY, "");
}
if (!values.containsKey(Contains.SHARE_MODIFIED_BY)) {
values.put(Contains.SHARE_MODIFIED_BY, "");
}
}
private Uri insertStore(Uri url, ContentValues values) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
long rowID;
Long now = Long.valueOf(System.currentTimeMillis());
// Make sure that the fields are all set
if (!values.containsKey(Stores.NAME)) {
throw new SQLException("Failed to insert row into " + url
+ ": Store NAME must be given.");
}
if (!values.containsKey(Stores.CREATED_DATE)) {
values.put(Stores.CREATED_DATE, now);
}
if (!values.containsKey(Stores.MODIFIED_DATE)) {
values.put(Stores.MODIFIED_DATE, now);
}
// TODO: Here we should check, whether item exists already.
// (see TagsProvider)
// insert the tag.
rowID = db.insert("stores", "stores", values);
if (rowID > 0) {
Uri uri = ContentUris.withAppendedId(Stores.CONTENT_URI, rowID);
getContext().getContentResolver().notifyChange(uri, null);
Intent intent = new Intent(ProviderIntents.ACTION_INSERTED);
intent.setData(uri);
getContext().sendBroadcast(intent);
return uri;
}
// If everything works, we should not reach the following line:
throw new SQLException("Failed to insert row into " + url);
}
private Uri insertItemStore(Uri url, ContentValues values) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
Long now = Long.valueOf(System.currentTimeMillis());
// Make sure that the fields are all set
if (!(values.containsKey(ItemStores.ITEM_ID) && values
.containsKey(ItemStores.STORE_ID))) {
// At least these values should exist.
throw new SQLException("Failed to insert row into " + url
+ ": ITEM_ID and STORE_ID must be given.");
}
// TODO: Check here that ITEM_ID and STORE_ID
// actually exist in the tables.
if (!values.containsKey(ItemStores.PRICE)) {
values.put(ItemStores.PRICE, -1);
}
if (!values.containsKey(ItemStores.AISLE)) {
values.putNull(ItemStores.AISLE);
}
if (!values.containsKey(ItemStores.CREATED_DATE)) {
values.put(ItemStores.CREATED_DATE, now);
}
if (!values.containsKey(ItemStores.MODIFIED_DATE)) {
values.put(ItemStores.MODIFIED_DATE, now);
}
// TODO: Here we should check, whether item exists already.
// (see TagsProvider)
// insert the item.
long rowId = db.insert("itemstores", "itemstores", values);
if (rowId > 0) {
Uri uri = ContentUris.withAppendedId(ItemStores.CONTENT_URI, rowId);
getContext().getContentResolver().notifyChange(uri, null);
Intent intent = new Intent(ProviderIntents.ACTION_INSERTED);
intent.setData(uri);
getContext().sendBroadcast(intent);
return uri;
}
// If everything works, we should not reach the following line:
throw new SQLException("Failed to insert row into " + url);
}
private Uri insertUnits(Uri url, ContentValues values) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
long rowID;
Long now = Long.valueOf(System.currentTimeMillis());
// Make sure that the fields are all set
if (!values.containsKey(Units.NAME)) {
throw new SQLException("Failed to insert row into " + url
+ ": Units NAME must be given.");
}
if (!values.containsKey(Units.CREATED_DATE)) {
values.put(Units.CREATED_DATE, now);
}
if (!values.containsKey(Stores.MODIFIED_DATE)) {
values.put(Units.MODIFIED_DATE, now);
}
// TODO: Here we should check, whether item exists already.
// (see TagsProvider)
// insert the units.
rowID = db.insert("units", "units", values);
if (rowID > 0) {
Uri uri = ContentUris.withAppendedId(Units.CONTENT_URI, rowID);
getContext().getContentResolver().notifyChange(uri, null);
Intent intent = new Intent(ProviderIntents.ACTION_INSERTED);
intent.setData(uri);
getContext().sendBroadcast(intent);
return uri;
}
// If everything works, we should not reach the following line:
throw new SQLException("Failed to insert row into " + url);
}
@Override
public int delete(Uri url, String where, String[] whereArgs) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int count;
long[] affectedRows = null;
// long rowId;
switch (URL_MATCHER.match(url)) {
case ITEMS:
affectedRows = ProviderUtils.getAffectedRows(db, "items", where,
whereArgs);
count = db.delete("items", where, whereArgs);
break;
case ITEM_ID:
String segment = url.getPathSegments().get(1); // contains rowId
// rowId = Long.parseLong(segment);
String whereString;
if (!TextUtils.isEmpty(where)) {
whereString = " AND (" + where + ')';
} else {
whereString = "";
}
affectedRows = ProviderUtils.getAffectedRows(db, "items", "_id="
+ segment + whereString, whereArgs);
count = db.delete("items", "_id=" + segment + whereString,
whereArgs);
break;
case LISTS:
affectedRows = ProviderUtils.getAffectedRows(db, "lists", where,
whereArgs);
count = db.delete("lists", where, whereArgs);
break;
case LIST_ID:
segment = url.getPathSegments().get(1); // contains rowId
// rowId = Long.parseLong(segment);
if (!TextUtils.isEmpty(where)) {
whereString = " AND (" + where + ')';
} else {
whereString = "";
}
affectedRows = ProviderUtils.getAffectedRows(db, "lists", "_id="
+ segment + whereString, whereArgs);
count = db.delete("lists", "_id=" + segment + whereString,
whereArgs);
break;
case CONTAINS:
affectedRows = ProviderUtils.getAffectedRows(db, "contains", where,
whereArgs);
count = db.delete("contains", where, whereArgs);
break;
case CONTAINS_ID:
segment = url.getPathSegments().get(1); // contains rowId
// rowId = Long.parseLong(segment);
if (!TextUtils.isEmpty(where)) {
whereString = " AND (" + where + ')';
} else {
whereString = "";
}
affectedRows = ProviderUtils.getAffectedRows(db, "contains", "_id="
+ segment + whereString, whereArgs);
count = db.delete("contains", "_id=" + segment + whereString,
whereArgs);
break;
case NOTE_ID:
// don't delete the row, just the note.
ContentValues values = new ContentValues();
values.putNull("note");
count = update(url, values, null, null);
break;
case STORES:
affectedRows = ProviderUtils.getAffectedRows(db, "stores", where,
whereArgs);
count = db.delete("stores", where, whereArgs);
break;
case ITEMSTORES:
affectedRows = ProviderUtils.getAffectedRows(db, "itemstores",
where, whereArgs);
count = db.delete("itemstores", where, whereArgs);
break;
case ITEMSTORES_ID:
segment = url.getPathSegments().get(1); // contains rowId
// rowId = Long.parseLong(segment);
if (!TextUtils.isEmpty(where)) {
whereString = " AND (" + where + ')';
} else {
whereString = "";
}
affectedRows = ProviderUtils.getAffectedRows(db, "itemstores",
"_id=" + segment + whereString, whereArgs);
count = db.delete("itemstores", "_id=" + segment + whereString,
whereArgs);
break;
default:
throw new IllegalArgumentException("Unknown URL " + url);
}
getContext().getContentResolver().notifyChange(url, null);
Intent intent = new Intent(ProviderIntents.ACTION_DELETED);
intent.setData(url);
intent.putExtra(ProviderIntents.EXTRA_AFFECTED_ROWS, affectedRows);
getContext().sendBroadcast(intent);
return count;
}
@Override
public int update(Uri url, ContentValues values, String where,
String[] whereArgs) {
if (debug) {
Log.d(TAG, "update called for: " + url);
}
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int count;
Uri secondUri = null;
// long rowId;
switch (URL_MATCHER.match(url)) {
case ITEMS:
case NOTES:
count = db.update("items", values, where, whereArgs);
break;
case NOTE_ID:
// drop some OI Notepad fields on the floor.
values.remove("title");
values.remove("encrypted");
values.remove("theme");
values.remove("nothing_To_see_here");
// fall through...
case ITEM_ID:
String segment = url.getPathSegments().get(1); // contains rowId
// rowId = Long.parseLong(segment);
String whereString;
if (!TextUtils.isEmpty(where)) {
whereString = " AND (" + where + ')';
} else {
whereString = "";
}
count = db.update("items", values, "_id=" + segment + whereString,
whereArgs);
secondUri = ShoppingContract.Items.CONTENT_URI;
break;
case LISTS:
count = db.update("lists", values, where, whereArgs);
break;
case LIST_ID:
segment = url.getPathSegments().get(1); // contains rowId
// rowId = Long.parseLong(segment);
if (!TextUtils.isEmpty(where)) {
whereString = " AND (" + where + ')';
} else {
whereString = "";
}
count = db.update("lists", values, "_id=" + segment + whereString,
whereArgs);
break;
case CONTAINS:
count = db.update("contains", values, where, whereArgs);
break;
case CONTAINS_ID:
segment = url.getPathSegments().get(1); // contains rowId
// rowId = Long.parseLong(segment);
if (!TextUtils.isEmpty(where)) {
whereString = " AND (" + where + ')';
} else {
whereString = "";
}
count = db.update("contains", values, "_id=" + segment
+ whereString, whereArgs);
break;
case STORES:
count = db.update("stores", values, where, whereArgs);
break;
case STORES_ID:
segment = url.getPathSegments().get(1); // contains rowId
// rowId = Long.parseLong(segment);
if (!TextUtils.isEmpty(where)) {
whereString = " AND (" + where + ')';
} else {
whereString = "";
}
count = db.update("stores", values, "_id=" + segment + whereString,
whereArgs);
break;
case ITEMSTORES:
count = db.update("itemstores", values, where, whereArgs);
break;
case ITEMSTORES_ID:
segment = url.getPathSegments().get(1); // contains rowId
// rowId = Long.parseLong(segment);
if (!TextUtils.isEmpty(where)) {
whereString = " AND (" + where + ')';
} else {
whereString = "";
}
count = db.update("itemstores", values, "_id=" + segment
+ whereString, whereArgs);
break;
case UNITS:
count = db.update("units", values, where, whereArgs);
break;
case UNITS_ID:
segment = url.getPathSegments().get(1); // contains rowId
// rowId = Long.parseLong(segment);
if (!TextUtils.isEmpty(where)) {
whereString = " AND (" + where + ')';
} else {
whereString = "";
}
count = db.update("units", values, "_id=" + segment + whereString,
whereArgs);
break;
default:
Log.e(TAG, "Update received unknown URL: " + url);
throw new IllegalArgumentException("Unknown URL " + url);
}
getContext().getContentResolver().notifyChange(url, null);
if (secondUri != null) {
getContext().getContentResolver().notifyChange(secondUri, null);
}
Intent intent = new Intent(ProviderIntents.ACTION_MODIFIED);
intent.setData(url);
getContext().sendBroadcast(intent);
return count;
}
@Override
public String getType(Uri url) {
switch (URL_MATCHER.match(url)) {
case ITEMS:
return "vnd.android.cursor.dir/vnd.openintents.shopping.item";
case ITEM_ID:
return ShoppingContract.ITEM_TYPE;
case LISTS:
return "vnd.android.cursor.dir/vnd.openintents.shopping.list";
case LIST_ID:
return "vnd.android.cursor.item/vnd.openintents.shopping.list";
case CONTAINS:
return "vnd.android.cursor.dir/vnd.openintents.shopping.contains";
case CONTAINS_ID:
return "vnd.android.cursor.item/vnd.openintents.shopping.contains";
case CONTAINS_FULL:
return "vnd.android.cursor.dir/vnd.openintents.shopping.containsfull";
case CONTAINS_FULL_ID:
return "vnd.android.cursor.item/vnd.openintents.shopping.containsfull";
case CONTAINS_FULL_LISTID:
return "vnd.android.cursor.dir/vnd.openintents.shopping.containsfull";
case STORES:
return "vnd.android.cursor.dir/vnd.openintents.shopping.stores";
case STORES_ID:
case STORES_LISTID:
return "vnd.android.cursor.item/vnd.openintents.shopping.stores";
case NOTES:
return ShoppingContract.Notes.CONTENT_TYPE;
case NOTE_ID:
return ShoppingContract.Notes.CONTENT_ITEM_TYPE;
case ITEMSTORES:
return "vnd.android.cursor.dir/vnd.openintents.shopping.itemstores";
case ITEMSTORES_ID:
return "vnd.android.cursor.item/vnd.openintents.shopping.itemstores";
case ITEMSTORES_ITEMID:
return "vnd.android.cursor.dir/vnd.openintents.shopping.itemstores";
case UNITS:
return "vnd.android.cursor.dir/vnd.openintents.shopping.units";
case UNITS_ID:
return "vnd.android.cursor.item/vnd.openintents.shopping.units";
case ACTIVELIST:
// not sure this is quite right
return "vnd.android.cursor.item/vnd.openintents.shopping.list";
default:
throw new IllegalArgumentException("Unknown URL " + url);
}
}
static {
URL_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
URL_MATCHER.addURI("org.openintents.shopping", "items", ITEMS);
URL_MATCHER.addURI("org.openintents.shopping", "items/#", ITEM_ID);
URL_MATCHER.addURI("org.openintents.shopping", "lists", LISTS);
URL_MATCHER.addURI("org.openintents.shopping", "lists/active",
ACTIVELIST);
URL_MATCHER.addURI("org.openintents.shopping", "lists/#", LIST_ID);
URL_MATCHER.addURI("org.openintents.shopping", "contains", CONTAINS);
URL_MATCHER.addURI("org.openintents.shopping", "contains/#",
CONTAINS_ID);
URL_MATCHER.addURI("org.openintents.shopping", "contains/copyof/#",
CONTAINS_COPYOFID);
URL_MATCHER.addURI("org.openintents.shopping", "containsfull",
CONTAINS_FULL);
URL_MATCHER.addURI("org.openintents.shopping", "containsfull/#",
CONTAINS_FULL_ID);
URL_MATCHER.addURI("org.openintents.shopping", "containsfull/list/#",
CONTAINS_FULL_LISTID);
URL_MATCHER.addURI("org.openintents.shopping", "stores", STORES);
URL_MATCHER.addURI("org.openintents.shopping", "stores/#", STORES_ID);
URL_MATCHER
.addURI("org.openintents.shopping", "itemstores", ITEMSTORES);
URL_MATCHER.addURI("org.openintents.shopping", "itemstores/#",
ITEMSTORES_ID);
URL_MATCHER.addURI("org.openintents.shopping", "itemstores/item/#/#",
ITEMSTORES_ITEMID);
URL_MATCHER.addURI("org.openintents.shopping", "liststores/#",
STORES_LISTID);
URL_MATCHER.addURI("org.openintents.shopping", "listtags/#",
TAGS_LISTID);
URL_MATCHER.addURI("org.openintents.shopping", "notes", NOTES);
URL_MATCHER.addURI("org.openintents.shopping", "notes/#", NOTE_ID);
URL_MATCHER.addURI("org.openintents.shopping", "units", UNITS);
URL_MATCHER.addURI("org.openintents.shopping", "units/#", UNITS_ID);
URL_MATCHER.addURI("org.openintents.shopping", "prefs", PREFS);
// subtotals for the specified list id, or active list if not specified
URL_MATCHER.addURI("org.openintents.shopping", "subtotals/#",
SUBTOTALS_LISTID);
URL_MATCHER.addURI("org.openintents.shopping", "subtotals", SUBTOTALS);
ITEMS_PROJECTION_MAP = new HashMap<String, String>();
ITEMS_PROJECTION_MAP.put(Items._ID, "items._id");
ITEMS_PROJECTION_MAP.put(Items.NAME, "items.name");
ITEMS_PROJECTION_MAP.put(Items.IMAGE, "items.image");
ITEMS_PROJECTION_MAP.put(Items.PRICE, "items.price");
ITEMS_PROJECTION_MAP.put(Items.UNITS, "items.units");
ITEMS_PROJECTION_MAP.put(Items.TAGS, "items.tags");
ITEMS_PROJECTION_MAP.put(Items.BARCODE, "items.barcode");
ITEMS_PROJECTION_MAP.put(Items.LOCATION, "items.location");
ITEMS_PROJECTION_MAP.put(Items.DUE_DATE, "items.due");
ITEMS_PROJECTION_MAP.put(Items.CREATED_DATE, "items.created");
ITEMS_PROJECTION_MAP.put(Items.MODIFIED_DATE, "items.modified");
ITEMS_PROJECTION_MAP.put(Items.ACCESSED_DATE, "items.accessed");
LISTS_PROJECTION_MAP = new HashMap<String, String>();
LISTS_PROJECTION_MAP.put(Lists._ID, "lists._id");
LISTS_PROJECTION_MAP.put(Lists.NAME, "lists.name");
LISTS_PROJECTION_MAP.put(Lists.IMAGE, "lists.image");
LISTS_PROJECTION_MAP.put(Lists.CREATED_DATE, "lists.created");
LISTS_PROJECTION_MAP.put(Lists.MODIFIED_DATE, "lists.modified");
LISTS_PROJECTION_MAP.put(Lists.ACCESSED_DATE, "lists.accessed");
LISTS_PROJECTION_MAP.put(Lists.SHARE_NAME, "lists.share_name");
LISTS_PROJECTION_MAP.put(Lists.SHARE_CONTACTS, "lists.share_contacts");
LISTS_PROJECTION_MAP
.put(Lists.SKIN_BACKGROUND, "lists.skin_background");
LISTS_PROJECTION_MAP.put(Lists.SKIN_FONT, "lists.skin_font");
LISTS_PROJECTION_MAP.put(Lists.SKIN_COLOR, "lists.skin_color");
LISTS_PROJECTION_MAP.put(Lists.SKIN_COLOR_STRIKETHROUGH,
"lists.skin_color_strikethrough");
LISTS_PROJECTION_MAP.put(Lists.ITEMS_SORT, "lists.items_sort");
CONTAINS_PROJECTION_MAP = new HashMap<String, String>();
CONTAINS_PROJECTION_MAP.put(Contains._ID, "contains._id");
CONTAINS_PROJECTION_MAP.put(Contains.ITEM_ID, "contains.item_id");
CONTAINS_PROJECTION_MAP.put(Contains.LIST_ID, "contains.list_id");
CONTAINS_PROJECTION_MAP.put(Contains.QUANTITY, "contains.quantity");
CONTAINS_PROJECTION_MAP.put(Contains.PRIORITY, "contains.priority");
CONTAINS_PROJECTION_MAP.put(Contains.STATUS, "contains.status");
CONTAINS_PROJECTION_MAP.put(Contains.CREATED_DATE, "contains.created");
CONTAINS_PROJECTION_MAP
.put(Contains.MODIFIED_DATE, "contains.modified");
CONTAINS_PROJECTION_MAP
.put(Contains.ACCESSED_DATE, "contains.accessed");
CONTAINS_PROJECTION_MAP.put(Contains.SHARE_CREATED_BY,
"contains.share_created_by");
CONTAINS_PROJECTION_MAP.put(Contains.SHARE_MODIFIED_BY,
"contains.share_modified_by");
CONTAINS_FULL_PROJECTION_MAP = new HashMap<String, String>();
CONTAINS_FULL_PROJECTION_MAP.put(ContainsFull._ID,
"contains._id as _id");
CONTAINS_FULL_PROJECTION_MAP.put(ContainsFull.ITEM_ID,
"contains.item_id");
CONTAINS_FULL_PROJECTION_MAP.put(ContainsFull.LIST_ID,
"contains.list_id");
CONTAINS_FULL_PROJECTION_MAP.put(ContainsFull.QUANTITY,
"contains.quantity as quantity");
CONTAINS_FULL_PROJECTION_MAP.put(ContainsFull.PRIORITY,
"contains.priority as priority");
CONTAINS_FULL_PROJECTION_MAP
.put(ContainsFull.STATUS, "contains.status");
CONTAINS_FULL_PROJECTION_MAP.put(ContainsFull.CREATED_DATE,
"contains.created");
CONTAINS_FULL_PROJECTION_MAP.put(ContainsFull.MODIFIED_DATE,
"contains.modified");
CONTAINS_FULL_PROJECTION_MAP.put(ContainsFull.ACCESSED_DATE,
"contains.accessed");
CONTAINS_FULL_PROJECTION_MAP.put(ContainsFull.SHARE_CREATED_BY,
"contains.share_created_by");
CONTAINS_FULL_PROJECTION_MAP.put(ContainsFull.SHARE_MODIFIED_BY,
"contains.share_modified_by");
CONTAINS_FULL_PROJECTION_MAP.put(ContainsFull.ITEM_NAME,
"items.name as item_name");
CONTAINS_FULL_PROJECTION_MAP.put(ContainsFull.ITEM_IMAGE,
"items.image as item_image");
CONTAINS_FULL_PROJECTION_MAP.put(ContainsFull.ITEM_PRICE,
"items.price as item_price");
CONTAINS_FULL_PROJECTION_MAP.put(ContainsFull.ITEM_UNITS,
"items.units as item_units");
CONTAINS_FULL_PROJECTION_MAP.put(ContainsFull.ITEM_TAGS,
"items.tags as item_tags");
CONTAINS_FULL_PROJECTION_MAP.put(ContainsFull.LIST_NAME,
"lists.name as list_name");
CONTAINS_FULL_PROJECTION_MAP.put(ContainsFull.LIST_IMAGE,
"lists.image as list_image");
CONTAINS_FULL_PROJECTION_MAP.put(ContainsFull.ITEM_HAS_NOTE,
"items.note is not NULL and items.note <> '' as item_has_note");
CONTAINS_FULL_CHEAPEST_PROJECTION_MAP = new HashMap<String, String>(
CONTAINS_FULL_PROJECTION_MAP);
CONTAINS_FULL_CHEAPEST_PROJECTION_MAP.put(ContainsFull.ITEM_PRICE,
"min(itemstores.price) as item_price");
CONTAINS_FULL_STORE_PROJECTION_MAP = new HashMap<String, String>(
CONTAINS_FULL_PROJECTION_MAP);
CONTAINS_FULL_STORE_PROJECTION_MAP.put(ContainsFull.ITEM_PRICE,
"itemstores.price as item_price");
UNITS_PROJECTION_MAP = new HashMap<String, String>();
UNITS_PROJECTION_MAP.put(Units._ID, "units._id");
UNITS_PROJECTION_MAP.put(Units.CREATED_DATE, "units.created");
UNITS_PROJECTION_MAP.put(Units.MODIFIED_DATE, "units.modified");
UNITS_PROJECTION_MAP.put(Units.NAME, "units.name");
UNITS_PROJECTION_MAP.put(Units.SINGULAR, "units.singular");
STORES_PROJECTION_MAP = new HashMap<String, String>();
STORES_PROJECTION_MAP.put(Stores._ID, "stores._id");
STORES_PROJECTION_MAP.put(Stores.CREATED_DATE, "stores.created");
STORES_PROJECTION_MAP.put(Stores.MODIFIED_DATE, "stores.modified");
STORES_PROJECTION_MAP.put(Stores.NAME, "stores.name");
STORES_PROJECTION_MAP.put(Stores.LIST_ID, "stores.list_id");
ITEMSTORES_PROJECTION_MAP = new HashMap<String, String>();
ITEMSTORES_PROJECTION_MAP.put(ItemStores._ID, "itemstores._id");
ITEMSTORES_PROJECTION_MAP.put(ItemStores.CREATED_DATE,
"itemstores.created");
ITEMSTORES_PROJECTION_MAP.put(ItemStores.MODIFIED_DATE,
"itemstores.modified");
ITEMSTORES_PROJECTION_MAP.put(ItemStores.ITEM_ID, "itemstores.item_id");
ITEMSTORES_PROJECTION_MAP.put(ItemStores.STORE_ID,
"itemstores.store_id");
ITEMSTORES_PROJECTION_MAP.put(Stores.NAME, "stores.name");
ITEMSTORES_PROJECTION_MAP.put(ItemStores.AISLE, "itemstores.aisle");
ITEMSTORES_PROJECTION_MAP.put(ItemStores.PRICE, "itemstores.price");
ITEMSTORES_PROJECTION_MAP.put(ItemStores.STOCKS_ITEM,
"itemstores.stocks_item");
NOTES_PROJECTION_MAP = new HashMap<String, String>();
NOTES_PROJECTION_MAP.put(ShoppingContract.Notes._ID, "items._id");
NOTES_PROJECTION_MAP.put(ShoppingContract.Notes.NOTE, "items.note");
NOTES_PROJECTION_MAP.put(ShoppingContract.Notes.TITLE, "null as title");
NOTES_PROJECTION_MAP.put(ShoppingContract.Notes.TAGS, "null as tags");
NOTES_PROJECTION_MAP.put(ShoppingContract.Notes.ENCRYPTED,
"null as encrypted");
NOTES_PROJECTION_MAP.put(ShoppingContract.Notes.THEME, "null as theme");
SUBTOTALS_PROJECTION_MAP = new HashMap<String, String>();
SUBTOTALS_PROJECTION_MAP.put(ShoppingContract.Subtotals.COUNT,
"count() as count");
SUBTOTALS_PROJECTION_MAP.put(ShoppingContract.Subtotals.PRIORITY,
"priority");
SUBTOTALS_PROJECTION_MAP.put(ShoppingContract.Subtotals.SUBTOTAL,
"sum(qty_price) as subtotal");
SUBTOTALS_PROJECTION_MAP.put(ShoppingContract.Subtotals.STATUS,
"status");
}
}