├── res
└── .gitignore
├── lint.xml
├── example
├── lint.xml
├── res
│ ├── drawable-hdpi
│ │ └── ic_launcher.png
│ ├── drawable-ldpi
│ │ └── ic_launcher.png
│ ├── drawable-mdpi
│ │ └── ic_launcher.png
│ ├── drawable-xhdpi
│ │ └── ic_launcher.png
│ ├── xml
│ │ └── searchable.xml
│ ├── menu
│ │ ├── item_context.xml
│ │ ├── main.xml
│ │ └── item_edit.xml
│ ├── layout
│ │ ├── detail.xml
│ │ ├── dialog_filter.xml
│ │ ├── main.xml
│ │ └── edit.xml
│ └── values
│ │ └── strings.xml
├── .classpath
├── project.properties
├── proguard.cfg
├── src
│ └── edu
│ │ └── mit
│ │ └── mobile
│ │ └── android
│ │ └── content
│ │ └── example
│ │ ├── Message.java
│ │ ├── SampleProvider.java
│ │ └── MessageDetail.java
└── AndroidManifest.xml
├── .gitignore
├── test
├── res
│ ├── drawable-hdpi
│ │ └── icon.png
│ ├── drawable-ldpi
│ │ └── icon.png
│ ├── drawable-mdpi
│ │ └── icon.png
│ └── values
│ │ └── strings.xml
├── project.properties
├── src
│ └── edu
│ │ └── mit
│ │ └── mobile
│ │ └── android
│ │ └── content
│ │ └── test
│ │ ├── sample5
│ │ ├── Tag.java
│ │ ├── IdenticalTagFinder.java
│ │ ├── SampleProvider5.java
│ │ ├── Bookmark.java
│ │ └── SampleProvider5Test.java
│ │ ├── SampleProvider4.java
│ │ ├── sample3
│ │ ├── Person.java
│ │ └── Project.java
│ │ ├── SampleProvider1.java
│ │ ├── sample1
│ │ └── Message.java
│ │ ├── sample4
│ │ └── Person.java
│ │ ├── sample2
│ │ ├── Comment.java
│ │ └── BlogPost.java
│ │ ├── SampleProvider3.java
│ │ ├── ContentResolverTestUtils.java
│ │ ├── SampleProvider2.java
│ │ ├── query
│ │ ├── QueryBuilderTest.java
│ │ └── ParserTest.java
│ │ ├── SampleProvider1Test.java
│ │ ├── SampleProvider3Test.java
│ │ └── SampleProvider4Test.java
└── AndroidManifest.xml
├── Makefile
├── src
└── edu
│ └── mit
│ └── mobile
│ └── android
│ └── content
│ ├── package.html
│ ├── m2m
│ ├── M2MColumns.java
│ ├── IdenticalChildFinder.java
│ ├── M2MManager.java
│ └── M2MReverseHelper.java
│ ├── column
│ ├── BlobColumn.java
│ ├── TextColumn.java
│ ├── FloatColumn.java
│ ├── DoubleColumn.java
│ ├── IntegerColumn.java
│ ├── BooleanColumn.java
│ ├── TimestampColumn.java
│ ├── DBForeignKeyColumn.java
│ ├── DatetimeColumn.java
│ ├── DBColumnType.java
│ └── DBColumn.java
│ ├── SQLGenerationException.java
│ ├── ContentItem.java
│ ├── DBTable.java
│ ├── ContentItemRegisterable.java
│ ├── DBSortOrder.java
│ ├── UriPath.java
│ ├── OnSaveListener.java
│ ├── Manager.java
│ ├── AndroidVersions.java
│ ├── dbhelper
│ └── ContentItemDBHelper.java
│ ├── DBHelper.java
│ ├── SQLGenUtils.java
│ ├── ForeignKeyManager.java
│ ├── GenericDBHelper.java
│ ├── ForeignKeyDBHelper.java
│ ├── query
│ └── QuerystringParser.y
│ ├── ProviderUtils.java
│ └── DBHelperMapper.java
├── AndroidManifest.xml
├── project.properties
├── .classpath
└── README.md
/res/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
See {@link edu.mit.mobile.android.content.SimpleContentProvider} for an example of how to use this package.
7 | 8 | 9 | -------------------------------------------------------------------------------- /example/.classpath: -------------------------------------------------------------------------------- 1 | 2 |14 | * This represents a message. This class is never instantiated, it just contains static definitions 15 | * that can be used to access messages using the {@link ContentProvider} framework. 16 | *
17 | * 18 | *19 | * The database table name is a sanitized, lower-cased version of this classname. In this case it is 20 | * "message". You can override this by using the {@link DBTable} annotation on this class. 21 | *
22 | * 23 | */ 24 | public class Message implements ContentItem { 25 | 26 | // Column definitions /////////////////////////////////// 27 | 28 | // ContentItem contains one column definition for the BaseColumns._ID which 29 | // defines the primary key. 30 | 31 | // An example column that is automatically set to the current date/time. 32 | // The value of the string is the column name. 33 | @DBColumn(type = DatetimeColumn.class, defaultValue = DatetimeColumn.NOW_IN_MILLISECONDS) 34 | public static final String CREATED_DATE = "created"; 35 | 36 | // An example text column, representing 37 | @DBColumn(type = TextColumn.class) 38 | public static final String TITLE = "title"; 39 | 40 | @DBColumn(type = TextColumn.class) 41 | public static final String BODY = "body"; 42 | 43 | // ////////////////////////////////////////////////////// 44 | 45 | // This defines the path component of the content URI. 46 | // For most instances, it's best to just use the classname here: 47 | public static final String PATH = "message"; 48 | 49 | // The SimpleContentProvider constructs content URIs based on your provided 50 | // path and authority. 51 | // This constant is not necessary, but is very handy for doing queries. 52 | public static final Uri CONTENT_URI = ProviderUtils.toContentUri( 53 | SampleProvider.AUTHORITY, PATH); 54 | 55 | /** 56 | *57 | * The content type representing a message item. 58 | *
59 | * 60 | *61 | * This content type is automatically generated based on the provider name and the table name. 62 | * See {@link ProviderUtils#toItemType(String, String)} for details. 63 | *
64 | */ 65 | public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd.edu.mit.mobile.android.content.example.sampleprovider.message"; 66 | 67 | /** 68 | * The content type representing a list of messages. 69 | */ 70 | public static final String CONTENT_TYPE_DIR = "vnd.android.cursor.dir/vnd.edu.mit.mobile.android.content.example.sampleprovider.message"; 71 | 72 | } 73 | -------------------------------------------------------------------------------- /test/src/edu/mit/mobile/android/content/test/SampleProvider2.java: -------------------------------------------------------------------------------- 1 | package edu.mit.mobile.android.content.test; 2 | 3 | /* 4 | * Copyright (C) 2011-2012 MIT Mobile Experience Lab 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License 8 | * as published by the Free Software Foundation; either version 2 9 | * of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | import android.net.Uri; 21 | import edu.mit.mobile.android.content.ForeignKeyDBHelper; 22 | import edu.mit.mobile.android.content.GenericDBHelper; 23 | import edu.mit.mobile.android.content.ProviderUtils; 24 | import edu.mit.mobile.android.content.QuerystringWrapper; 25 | import edu.mit.mobile.android.content.SimpleContentProvider; 26 | import edu.mit.mobile.android.content.dbhelper.SearchDBHelper; 27 | import edu.mit.mobile.android.content.test.sample2.BlogPost; 28 | import edu.mit.mobile.android.content.test.sample2.Comment; 29 | 30 | public class SampleProvider2 extends SimpleContentProvider { 31 | public static final String AUTHORITY = "edu.mit.mobile.android.content.test.sampleprovider2"; 32 | 33 | public static final String SEARCH_PATH = null; // use the default search path 34 | public static final Uri SEARCH = ProviderUtils.toContentUri(AUTHORITY, 35 | getSearchPath(SEARCH_PATH)); 36 | 37 | public SampleProvider2() { 38 | // authority DB ver 39 | super(AUTHORITY, 1); 40 | 41 | final GenericDBHelper blogPostsRaw = new GenericDBHelper(BlogPost.class); 42 | final QuerystringWrapper blogPosts = new QuerystringWrapper(blogPostsRaw); 43 | 44 | blogPosts.setOnSaveListener(BlogPost.ON_SAVE_LISTENER); 45 | 46 | // creates a relationship between BlogPosts and Comments, using Comment.POST as the column. 47 | // It's also responsible for creating the tables for the child. 48 | final ForeignKeyDBHelper comments = new ForeignKeyDBHelper(BlogPost.class, Comment.class, 49 | Comment.POST); 50 | 51 | addDirAndItemUri(blogPosts, BlogPost.PATH); 52 | addChildDirAndItemUri(comments, BlogPost.PATH, Comment.PATH); 53 | 54 | addDirAndItemUri(comments, Comment.PATH_ALL_COMMENTS); 55 | 56 | // add in a search interface 57 | final SearchDBHelper searchHelper = new SearchDBHelper(); 58 | 59 | searchHelper.registerDBHelper(blogPostsRaw, BlogPost.CONTENT_URI, BlogPost.TITLE, 60 | BlogPost.BODY, BlogPost.BODY, BlogPost.TITLE); 61 | 62 | searchHelper.registerDBHelper(comments, Comment.ALL_COMMENTS, Comment.BODY, null, 63 | Comment.BODY); 64 | 65 | addSearchUri(searchHelper, SEARCH_PATH); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/edu/mit/mobile/android/content/m2m/M2MManager.java: -------------------------------------------------------------------------------- 1 | package edu.mit.mobile.android.content.m2m; 2 | 3 | /* 4 | * Copyright (C) 2011-2013 MIT Mobile Experience Lab 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License version 2.1 as published by the Free Software Foundation. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this library; if not, visit 17 | * http://www.gnu.org/licenses/lgpl.html 18 | */ 19 | import android.content.ContentResolver; 20 | import android.content.ContentValues; 21 | import android.database.Cursor; 22 | import android.net.Uri; 23 | import edu.mit.mobile.android.content.ContentItem; 24 | import edu.mit.mobile.android.content.DBSortOrder; 25 | import edu.mit.mobile.android.content.Manager; 26 | import edu.mit.mobile.android.content.UriPath; 27 | 28 | public class M2MManager implements Manager { 29 | private final Class extends ContentItem> mTo; 30 | private final String mPath; 31 | private final String mSortOrder; 32 | 33 | public M2MManager(Class extends ContentItem> to) { 34 | mTo = to; 35 | final UriPath path = mTo.getAnnotation(UriPath.class); 36 | mPath = path != null ? path.value() : null; 37 | final DBSortOrder sortOrder = mTo.getAnnotation(DBSortOrder.class); 38 | mSortOrder = sortOrder != null ? sortOrder.value() : null; 39 | } 40 | 41 | /* 42 | * (non-Javadoc) 43 | * 44 | * @see edu.mit.mobile.android.content.m2m.Manager#getUri(android.net.Uri) 45 | */ 46 | @Override 47 | public Uri getUri(Uri parent) { 48 | return Uri.withAppendedPath(parent, mPath); 49 | } 50 | 51 | /* 52 | * (non-Javadoc) 53 | * 54 | * @see edu.mit.mobile.android.content.m2m.Manager#insert(android.content.ContentResolver, 55 | * android.net.Uri, android.content.ContentValues) 56 | */ 57 | @Override 58 | public Uri insert(ContentResolver cr, Uri parent, ContentValues cv) { 59 | return cr.insert(getUri(parent), cv); 60 | } 61 | 62 | /* 63 | * (non-Javadoc) 64 | * 65 | * @see edu.mit.mobile.android.content.m2m.Manager#query(android.content.ContentResolver, 66 | * android.net.Uri, java.lang.String[]) 67 | */ 68 | @Override 69 | public Cursor query(ContentResolver cr, Uri parent, String[] projection) { 70 | return cr.query(getUri(parent), projection, null, null, mSortOrder); 71 | } 72 | 73 | /* 74 | * (non-Javadoc) 75 | * 76 | * @see edu.mit.mobile.android.content.m2m.Manager#getSortOrder() 77 | */ 78 | @Override 79 | public String getSortOrder() { 80 | return mSortOrder; 81 | } 82 | 83 | /* 84 | * (non-Javadoc) 85 | * 86 | * @see edu.mit.mobile.android.content.m2m.Manager#getPath() 87 | */ 88 | @Override 89 | public String getPath() { 90 | return mPath; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/edu/mit/mobile/android/content/dbhelper/ContentItemDBHelper.java: -------------------------------------------------------------------------------- 1 | package edu.mit.mobile.android.content.dbhelper; 2 | 3 | /* 4 | * Copyright (C) 2013 MIT Mobile Experience Lab 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License version 2.1 as published by the Free Software Foundation. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public 16 | * License along with this library; if not, visit 17 | * http://www.gnu.org/licenses/lgpl.html 18 | */ 19 | import edu.mit.mobile.android.content.ContentItem; 20 | import edu.mit.mobile.android.content.ContentItemRegisterable; 21 | import edu.mit.mobile.android.content.DBHelper; 22 | import edu.mit.mobile.android.content.DBSortOrder; 23 | 24 | /** 25 | * This class adds in registration of {@link ContentItem}s, implementing the 26 | * {@link ContentItemRegisterable} interface. This also extracts the {@link DBSortOrder} annotation 27 | * from the ContentItem, making it available with {@link #getDefaultSortOrder()}. If your DBHelper 28 | * is based on ContentItems, it should be built off this class. 29 | * 30 | * @author Steve Pomeroy 31 | * 32 | */ 33 | public abstract class ContentItemDBHelper extends DBHelper implements ContentItemRegisterable { 34 | 35 | protected final Class extends ContentItem> mContentDir; 36 | protected final Class extends ContentItem> mContentItem; 37 | 38 | protected final String mSortOrder; 39 | 40 | /** 41 | * @param contentItem 42 | * the ContentItem that should be used for all queries passed to this DBHelper. 43 | */ 44 | public ContentItemDBHelper(Class extends ContentItem> contentItem) { 45 | mContentDir = contentItem; 46 | mContentItem = contentItem; 47 | mSortOrder = extractSortOrder(); 48 | } 49 | 50 | /** 51 | * @param contentDir 52 | * the ContentItem which should be used for dir queries 53 | * @param contentItem 54 | * the ContentItem which should be used for item queries 55 | */ 56 | public ContentItemDBHelper(Class extends ContentItem> contentDir, 57 | Class extends ContentItem> contentItem) { 58 | mContentItem = contentItem; 59 | mContentDir = contentDir; 60 | mSortOrder = extractSortOrder(); 61 | } 62 | 63 | private String extractSortOrder() { 64 | final DBSortOrder sortOrder = mContentItem.getAnnotation(DBSortOrder.class); 65 | return sortOrder != null ? sortOrder.value() : null; 66 | } 67 | 68 | /** 69 | * Gets the sort order that was specified by the @{@link DBSortOrder} annotation. 70 | * 71 | * @return the default sort order on null if there was none specified 72 | */ 73 | public String getDefaultSortOrder() { 74 | return mSortOrder; 75 | } 76 | 77 | @Override 78 | public Class extends ContentItem> getContentItem(boolean isItem) { 79 | return isItem ? mContentItem : mContentDir; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /example/res/layout/edit.xml: -------------------------------------------------------------------------------- 1 | 2 |14 | * This creates a ContentProvider (which you must define in your AndroidManifest.xml) that you can 15 | * use to store all your data. You should only need one ContentProvider per Application, as each 16 | * ContentProvider can contain multiple types of data. 17 | *
18 | * 19 | *20 | * ContentProviders are accessed by querying content:// URIs. Content URIs have two types: "dir" and 21 | * "item". "dir" indicates that the URI represents a collection of logical items and "item" 22 | * indicates that it refers to a unique item. In {@link SimpleContentProvider}-generated providers, 23 | * this usually means that "dir" is a list of all the content of a given type, while "item" is an 24 | * individual content item, identified uniquely by its serial database row ID 25 | * {@link ContentItem#_ID}. 26 | *
27 | * 28 | *29 | * A "dir" URI ends in a non-numerical path: 30 | *
31 | * 32 | *33 | * content://AUTHORITY/PATH 34 | *35 | *
36 | * For this example, that would be: 37 | *
38 | * 39 | *40 | * content://edu.mit.mobile.android.content.example.sampleprovider/message 41 | *42 | * 43 | *
44 | * To make it easier to query in the future, that URI is stored in {@link Message#CONTENT_URI}. 45 | *
46 | * 47 | *48 | * An "item" URI builds off the "dir" URI and ends in a number. 49 | *
50 | * 51 | *52 | * content://AUTHORITY/PATH/# 53 | *54 | *
55 | * For this example, that would be: 56 | *
57 | * 58 | *59 | * content://edu.mit.mobile.android.content.example.sampleprovider/message/3 60 | *61 | * 62 | *
63 | * URIs of this type can be constructed using 64 | * {@link ContentUris#withAppendedId(android.net.Uri, long)}. 65 | *
66 | */ 67 | public class SampleProvider extends SimpleContentProvider { 68 | 69 | // Each ContentProvider must have a globally-unique authority. You can choose an arbitrary 70 | // string here, however to ensure that they will be globally-unique, best-practice is to build 71 | // one off your Application's package string. 72 | public static final String AUTHORITY = "edu.mit.mobile.android.content.example.sampleprovider"; 73 | 74 | public static final String SEARCH_PATH = null; 75 | 76 | public static final Uri SEARCH = ProviderUtils.toContentUri(AUTHORITY, 77 | getSearchPath(SEARCH_PATH)); 78 | 79 | // Every time you update your database schema, you must increment the 80 | // database version. 81 | private static final int DB_VERSION = 1; 82 | 83 | public SampleProvider() { 84 | super(AUTHORITY, DB_VERSION); 85 | 86 | // This helper is responsible for creating the tables and performing the actual database 87 | // queries. See Message for more info. 88 | final GenericDBHelper messageHelper = new GenericDBHelper(Message.class); 89 | 90 | // By wrapping the main helper like so, this will translate the query portion of the URI 91 | // (that is, the part after the "?") into a select statement to limit the results. 92 | final QuerystringWrapper queryWrapper = new QuerystringWrapper(messageHelper); 93 | 94 | // This adds a mapping between the given content:// URI path and the 95 | // helper. 96 | addDirAndItemUri(queryWrapper, Message.PATH); 97 | 98 | // the above statements can be repeated to create multiple data 99 | // stores. Each will have separate tables and URIs. 100 | 101 | // this hooks in search 102 | final SearchDBHelper searchHelper = new SearchDBHelper(); 103 | 104 | searchHelper.registerDBHelper(messageHelper, Message.CONTENT_URI, Message.TITLE, 105 | Message.BODY, Message.TITLE, Message.BODY); 106 | 107 | addSearchUri(searchHelper, SEARCH_PATH); 108 | 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /test/src/edu/mit/mobile/android/content/test/sample2/BlogPost.java: -------------------------------------------------------------------------------- 1 | package edu.mit.mobile.android.content.test.sample2; 2 | 3 | /* 4 | * Copyright (C) 2011 MIT Mobile Experience Lab 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License 8 | * as published by the Free Software Foundation; either version 2 9 | * of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | import android.content.ContentValues; 21 | import android.database.sqlite.SQLiteDatabase; 22 | import android.net.Uri; 23 | import edu.mit.mobile.android.content.ContentItem; 24 | import edu.mit.mobile.android.content.DBSortOrder; 25 | import edu.mit.mobile.android.content.DBTable; 26 | import edu.mit.mobile.android.content.ForeignKeyManager; 27 | import edu.mit.mobile.android.content.OnSaveListener; 28 | import edu.mit.mobile.android.content.ProviderUtils; 29 | import edu.mit.mobile.android.content.UriPath; 30 | import edu.mit.mobile.android.content.column.DBColumn; 31 | import edu.mit.mobile.android.content.column.DatetimeColumn; 32 | import edu.mit.mobile.android.content.column.TextColumn; 33 | import edu.mit.mobile.android.content.test.SampleProvider2; 34 | 35 | /** 36 | * A slightly more complex example to test. Doesn't entirely make sense as a data item on a phone, 37 | * but serves as a nice, well-understood demonstration and test. 38 | * 39 | * @author steve 40 | * 41 | */ 42 | @DBTable(BlogPost.TABLE) 43 | @UriPath(BlogPost.PATH) 44 | @DBSortOrder(BlogPost.SORT_ORDER_DEFAULT) 45 | public class BlogPost implements ContentItem { 46 | 47 | // Defining the table name as a static string will let you use it in your 48 | // content provider if you ever need to do custom DB queries. 49 | public static final String TABLE = "posts"; 50 | 51 | // Column definitions below. ContentItem contains one column definition 52 | // for the BaseColumns._ID which defines the primary key. 53 | @DBColumn(type = DatetimeColumn.class, defaultValue = DatetimeColumn.NOW_IN_MILLISECONDS) 54 | public static final String CREATED_DATE = "created"; 55 | 56 | @DBColumn(type = DatetimeColumn.class, defaultValue = DatetimeColumn.NOW_IN_MILLISECONDS, flags = DatetimeColumn.FLAG_AUTO_NOW) 57 | public static final String MODIFIED_DATE = "modified"; 58 | 59 | @DBColumn(type = TextColumn.class, notnull = true) 60 | public static final String TITLE = "title"; 61 | 62 | @DBColumn(type = TextColumn.class, notnull = true) 63 | public static final String BODY = "body"; 64 | 65 | @DBColumn(type = TextColumn.class, unique = true, notnull = true) 66 | public static final String SLUG = "slug"; 67 | 68 | // The path component of the content URI. 69 | public static final String PATH = "posts"; 70 | 71 | // the DBSortOrder annotation on this class denotes the default sort order. 72 | public static final String SORT_ORDER_DEFAULT = CREATED_DATE + " DESC"; 73 | 74 | // This is a helpful tool connecting back to the "child" of this object. This is similar 75 | // to Django's relation manager, although we need to define it ourselves. 76 | public static final ForeignKeyManager COMMENTS = new ForeignKeyManager(Comment.class); 77 | 78 | // The SimpleContentProvider constructs content URIs based on your provided 79 | // path and authority. 80 | // This constant is not necessary, but is very handy for doing queries. 81 | public static final Uri CONTENT_URI = ProviderUtils.toContentUri(SampleProvider2.AUTHORITY, 82 | PATH); 83 | 84 | public static final OnSaveListener ON_SAVE_LISTENER = new OnSaveListener() { 85 | @Override 86 | public ContentValues onPreSave(SQLiteDatabase db, Uri uri, ContentValues cv) { 87 | if (!cv.containsKey(SLUG) && cv.containsKey(TITLE)) { 88 | final String slug = cv.getAsString(TITLE).replaceAll("\\s+", "-") 89 | .replaceAll("[^\\w-]+", ""); 90 | cv.put(SLUG, slug); 91 | } 92 | return cv; 93 | } 94 | }; 95 | } 96 | -------------------------------------------------------------------------------- /src/edu/mit/mobile/android/content/column/DBColumn.java: -------------------------------------------------------------------------------- 1 | package edu.mit.mobile.android.content.column; 2 | 3 | /* 4 | * Copyright (C) 2011 MIT Mobile Experience Lab 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 2.1 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | import java.lang.annotation.Documented; 21 | import java.lang.annotation.ElementType; 22 | import java.lang.annotation.Retention; 23 | import java.lang.annotation.RetentionPolicy; 24 | import java.lang.annotation.Target; 25 | 26 | import edu.mit.mobile.android.content.SimpleContentProvider; 27 | 28 | /** 29 | * This defines a database column for use with the {@link SimpleContentProvider} framework. This 30 | * should be used on static final Strings, the value of which defines the column name. Various 31 | * column definition parameters can be set. 32 | * 33 | * eg.: 34 | * 35 | *36 | * @DBColumn(type=IntegerColumn.class) 37 | * final static String MY_COL = "my_col" 38 | *39 | * 40 | * The names and structure of this are based loosely on Django's Model/Field framework. 41 | * 42 | * @author Steve Pomeroy 43 | * 44 | */ 45 | @Retention(RetentionPolicy.RUNTIME) 46 | @Target(ElementType.FIELD) 47 | @Documented 48 | public @interface DBColumn { 49 | 50 | // this is required because Java doesn't allow null as a default value. 51 | // For some reason, null is not considered a constant expression. 52 | public static final String NULL = "██████NULL██████"; 53 | 54 | public static final long NULL_LONG = Long.MIN_VALUE; 55 | public static final int NULL_INT = Integer.MIN_VALUE; 56 | public static final float NULL_FLOAT = Float.MIN_VALUE; 57 | public static final double NULL_DOUBLE = Double.MIN_VALUE; 58 | 59 | /** 60 | * Specify one of the column types by passing its class object. 61 | * 62 | * 63 | * @see IntegerColumn 64 | * @see TextColumn 65 | * @see TimestampColumn 66 | * @see DatetimeColumn 67 | * 68 | * @return the column type 69 | */ 70 | Class extends DBColumnType>> type(); 71 | 72 | /** 73 | * Sets this column to be NOT NULL. 74 | * 75 | * @return true if the column is NOT NULL 76 | */ 77 | boolean notnull() default false; 78 | 79 | /** 80 | * Sets this column to be a PRIMARY KEY. 81 | * 82 | * @return true if the column is a primary key 83 | */ 84 | boolean primaryKey() default false; 85 | 86 | /** 87 | * Adds the AUTOINCREMENT flag if {@link #primaryKey()} has also been set. 88 | * 89 | * @return true if this column should be auto-incremented 90 | */ 91 | boolean autoIncrement() default false; 92 | 93 | /** 94 | * Sets a default value for the column. Values are automatically quoted as strings in SQL. To 95 | * avoid escaping (for use with reserved words and such), prefix with 96 | * {@link DBColumnType#DEFAULT_VALUE_ESCAPE}. 97 | * 98 | * @return the default value 99 | */ 100 | String defaultValue() default NULL; 101 | 102 | /** 103 | * Sets the default value for the column. 104 | * 105 | * @return the default value 106 | */ 107 | int defaultValueInt() default NULL_INT; 108 | 109 | /** 110 | * Sets the default value for the column. 111 | * 112 | * @return the default value 113 | */ 114 | long defaultValueLong() default NULL_LONG; 115 | 116 | /** 117 | * Sets the default value for the column. 118 | * 119 | * @return the default value 120 | */ 121 | float defaultValueFloat() default NULL_FLOAT; 122 | 123 | /** 124 | * Sets the default value for the column. 125 | * 126 | * @return the default value 127 | */ 128 | double defaultValueDouble() default NULL_DOUBLE; 129 | 130 | /** 131 | * If true, ensures that this column is unique. 132 | * 133 | * @return true if this column is UNIQUE 134 | */ 135 | boolean unique() default false; 136 | 137 | public static enum OnConflict { 138 | UNSPECIFIED, ROLLBACK, ABORT, FAIL, IGNORE, REPLACE 139 | } 140 | 141 | /** 142 | * If the column is marked unique, this determines what to do if there's a conflict. This is 143 | * ignored if the column is not unique. 144 | * 145 | * @return the desired conflict resolution 146 | */ 147 | OnConflict onConflict() default OnConflict.UNSPECIFIED; 148 | 149 | public static enum CollationName { 150 | DEFAULT, BINARY, NOCASE, RTRIM 151 | } 152 | 153 | /** 154 | * Defines a collation for the column. 155 | * 156 | * @return the collation type 157 | */ 158 | CollationName collate() default CollationName.DEFAULT; 159 | 160 | /** 161 | * Suffixes the column declaration with this string. 162 | * 163 | * @return a string of any supplemental column declarations 164 | */ 165 | String extraColDef() default NULL; 166 | 167 | /** 168 | * Column type-specific flags. 169 | * 170 | * @return 171 | */ 172 | int flags() default 0; 173 | } 174 | -------------------------------------------------------------------------------- /src/edu/mit/mobile/android/content/GenericDBHelper.java: -------------------------------------------------------------------------------- 1 | package edu.mit.mobile.android.content; 2 | /* 3 | * Copyright (C) 2011-2013 MIT Mobile Experience Lab 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License version 2.1 as published by the Free Software Foundation. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, visit 16 | * http://www.gnu.org/licenses/lgpl.html 17 | */ 18 | 19 | import android.content.ContentProvider; 20 | import android.content.ContentUris; 21 | import android.content.ContentValues; 22 | import android.database.Cursor; 23 | import android.database.SQLException; 24 | import android.database.sqlite.SQLiteDatabase; 25 | import android.net.Uri; 26 | import android.provider.BaseColumns; 27 | import edu.mit.mobile.android.content.annotation.SQLExtractor; 28 | import edu.mit.mobile.android.content.dbhelper.ContentItemDBHelper; 29 | 30 | /** 31 | * Provides basic CRUD database calls to handle very simple object types, eg: 32 | * 33 | *
34 | * content://AUTHORITY/item 35 | * content://AUTHORITY/item/1 36 | *37 | * 38 | * 39 | * @author Steve Pomeroy 40 | * 41 | */ 42 | public class GenericDBHelper extends ContentItemDBHelper { 43 | 44 | private final String mTable; 45 | private final SQLExtractor mExtractor; 46 | 47 | private boolean mCreatedTables = false; 48 | 49 | /** 50 | * @param contentItem 51 | * the class that defines the content item that will be managed by this helper. 52 | */ 53 | public GenericDBHelper(Class extends ContentItem> contentItem) { 54 | super(contentItem); 55 | mExtractor = new SQLExtractor(contentItem); 56 | mTable = mExtractor.getTableName(); 57 | } 58 | 59 | /** 60 | * This default implementation drops existing tables and recreates them. If you want to preserve 61 | * the user's data, please override this and handle migrations more carefully. 62 | * 63 | * @see edu.mit.mobile.android.content.DBHelper#upgradeTables(android.database.sqlite.SQLiteDatabase, 64 | * int, int) 65 | */ 66 | @Override 67 | public void upgradeTables(SQLiteDatabase db, int oldVersion, int newVersion) { 68 | db.execSQL("DROP TABLE IF EXISTS " + mTable); 69 | createTables(db); 70 | } 71 | 72 | public String getTable() { 73 | return mTable; 74 | } 75 | 76 | @Override 77 | public String getTargetTable() { 78 | return getTable(); 79 | } 80 | 81 | @Override 82 | public String getDirType(String authority, String path) { 83 | return ProviderUtils.toDirType(authority, mTable); 84 | } 85 | 86 | @Override 87 | public String getItemType(String authority, String path) { 88 | return ProviderUtils.toItemType(authority, mTable); 89 | } 90 | 91 | public Class extends ContentItem> getContentItem() { 92 | return mContentItem; 93 | } 94 | 95 | @Override 96 | public void createTables(SQLiteDatabase db) throws SQLGenerationException { 97 | if (mCreatedTables) { 98 | return; 99 | } 100 | for (final String sqlExpression : mExtractor.getTableCreation()) { 101 | db.execSQL(sqlExpression); 102 | } 103 | mCreatedTables = true; 104 | } 105 | 106 | protected ContentValues callOnPreSaveListener(SQLiteDatabase db, Uri uri, ContentValues values) { 107 | if (mOnSaveListener != null) { 108 | values = mOnSaveListener.onPreSave(db, null, values); 109 | } 110 | return values; 111 | } 112 | 113 | @Override 114 | public Uri insertDir(SQLiteDatabase db, ContentProvider provider, Uri uri, ContentValues values) 115 | throws SQLException { 116 | values = callOnPreSaveListener(db, uri, values); 117 | 118 | final long id = db.insertOrThrow(mTable, null, values); 119 | if (id != -1) { 120 | return ContentUris.withAppendedId(uri, id); 121 | } else { 122 | throw new SQLException("error inserting into " + mTable); 123 | } 124 | } 125 | 126 | @Override 127 | public int updateItem(SQLiteDatabase db, ContentProvider provider, Uri uri, 128 | ContentValues values, String where, String[] whereArgs) { 129 | 130 | values = callOnPreSaveListener(db, uri, values); 131 | 132 | return db.update(mTable, values, 133 | ProviderUtils.addExtraWhere(where, BaseColumns._ID + "=?"), 134 | ProviderUtils.addExtraWhereArgs(whereArgs, uri.getLastPathSegment())); 135 | } 136 | 137 | @Override 138 | public int updateDir(SQLiteDatabase db, ContentProvider provider, Uri uri, 139 | ContentValues values, String where, String[] whereArgs) { 140 | values = callOnPreSaveListener(db, uri, values); 141 | 142 | return db.update(mTable, values, where, whereArgs); 143 | } 144 | 145 | @Override 146 | public int deleteItem(SQLiteDatabase db, ContentProvider provider, Uri uri, String where, 147 | String[] whereArgs) { 148 | return db.delete(mTable, ProviderUtils.addExtraWhere(where, BaseColumns._ID + "=?"), 149 | ProviderUtils.addExtraWhereArgs(whereArgs, uri.getLastPathSegment())); 150 | } 151 | 152 | @Override 153 | public int deleteDir(SQLiteDatabase db, ContentProvider provider, Uri uri, String where, 154 | String[] whereArgs) { 155 | return db.delete(mTable, where, whereArgs); 156 | } 157 | 158 | @Override 159 | public Cursor queryDir(SQLiteDatabase db, Uri uri, String[] projection, String selection, 160 | String[] selectionArgs, String sortOrder) { 161 | 162 | return db.query(mTable, projection, selection, selectionArgs, null, null, 163 | sortOrder == null ? mSortOrder : sortOrder); 164 | 165 | } 166 | 167 | @Override 168 | public Cursor queryItem(SQLiteDatabase db, Uri uri, String[] projection, String selection, 169 | String[] selectionArgs, String sortOrder) { 170 | 171 | return db.query(mTable, projection, 172 | ProviderUtils.addExtraWhere(selection, BaseColumns._ID + "=?"), 173 | ProviderUtils.addExtraWhereArgs(selectionArgs, uri.getLastPathSegment()), null, 174 | null, sortOrder == null ? mSortOrder : sortOrder); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /test/src/edu/mit/mobile/android/content/test/SampleProvider3Test.java: -------------------------------------------------------------------------------- 1 | package edu.mit.mobile.android.content.test; 2 | 3 | import java.util.GregorianCalendar; 4 | import java.util.HashSet; 5 | 6 | import android.content.ContentResolver; 7 | import android.content.ContentUris; 8 | import android.database.Cursor; 9 | import android.net.Uri; 10 | import android.test.ProviderTestCase2; 11 | import android.test.mock.MockContentResolver; 12 | import edu.mit.mobile.android.content.m2m.M2MDBHelper; 13 | import edu.mit.mobile.android.content.m2m.M2MManager; 14 | import edu.mit.mobile.android.content.m2m.M2MReverseHelper; 15 | import edu.mit.mobile.android.content.test.sample3.Person; 16 | import edu.mit.mobile.android.content.test.sample3.Project; 17 | 18 | /** 19 | * Tests {@link M2MDBHelper}, {@link M2MManager}, and {@link M2MReverseHelper} 20 | * 21 | */ 22 | public class SampleProvider3Test extends ProviderTestCase2
31 | * relation 32 | * ↓ 33 | * [parent] → [child] 34 | * → [child 2] 35 | *36 | * 37 | * For example, you could have an BlogPost that has a relation to multiple Comments. 38 | * 39 | * Unfortunately, if your version of SQLite doesn't support foreign keys (see 40 | * {@link AndroidVersions}), this will not automatically cascade deletes for you or verify any 41 | * relationships. It will otherwise function, though; you will just need to cascade deletes by hand. 42 | * 43 | * The query function supports wildcard parent IDs. So to select all the children with any parent, 44 | * you can use {@link #WILDCARD_PATH_SEGMENT} instead of the parent's ID number. Eg.a path of 45 | * 46 | *
47 | * /parent/_all/child/ 48 | *49 | * 50 | * to get a list of all the children in any parent. 51 | * 52 | * @author steve 53 | * 54 | */ 55 | public class ForeignKeyDBHelper extends GenericDBHelper { 56 | public static final String WILDCARD_PATH_SEGMENT = "_all"; 57 | private final String mColumn; 58 | private final Class extends ContentItem> mChild; 59 | private final Class extends ContentItem> mParent; 60 | private final String mColumnQuoted; 61 | 62 | public ForeignKeyDBHelper(Class extends ContentItem> parent, 63 | Class extends ContentItem> child, String column) { 64 | super(child); 65 | mChild = child; 66 | mParent = parent; 67 | mColumn = column; 68 | mColumnQuoted = '"' + mColumn + '"'; 69 | } 70 | 71 | @Override 72 | public Uri insertDir(SQLiteDatabase db, ContentProvider provider, Uri uri, ContentValues values) 73 | throws SQLException { 74 | final long parentId = Long.valueOf(ProviderUtils.getNthPathFromEnd(uri, 1)); 75 | values.put(mColumn, parentId); 76 | return super.insertDir(db, provider, uri, values); 77 | } 78 | 79 | @Override 80 | public int updateItem(SQLiteDatabase db, ContentProvider provider, Uri uri, 81 | ContentValues values, String where, String[] whereArgs) { 82 | final String parentId = ProviderUtils.getNthPathFromEnd(uri, 2); 83 | 84 | return super.updateItem(db, provider, uri, values, 85 | ProviderUtils.addExtraWhere(where, mColumnQuoted + "=?"), 86 | ProviderUtils.addExtraWhereArgs(whereArgs, parentId)); 87 | } 88 | 89 | @Override 90 | public int updateDir(SQLiteDatabase db, ContentProvider provider, Uri uri, 91 | ContentValues values, String where, String[] whereArgs) { 92 | final String parentId = ProviderUtils.getNthPathFromEnd(uri, 1); 93 | 94 | return super.updateDir(db, provider, uri, values, 95 | ProviderUtils.addExtraWhere(where, mColumnQuoted + "=?"), 96 | ProviderUtils.addExtraWhereArgs(whereArgs, parentId)); 97 | } 98 | 99 | @Override 100 | public int deleteItem(SQLiteDatabase db, ContentProvider provider, Uri uri, String where, 101 | String[] whereArgs) { 102 | final String parentId = ProviderUtils.getNthPathFromEnd(uri, 2); 103 | 104 | return super.deleteItem(db, provider, uri, 105 | ProviderUtils.addExtraWhere(where, mColumnQuoted + "=?"), 106 | ProviderUtils.addExtraWhereArgs(whereArgs, parentId)); 107 | } 108 | 109 | @Override 110 | public int deleteDir(SQLiteDatabase db, ContentProvider provider, Uri uri, String where, 111 | String[] whereArgs) { 112 | final String parentId = ProviderUtils.getNthPathFromEnd(uri, 1); 113 | 114 | return super.deleteDir(db, provider, uri, 115 | ProviderUtils.addExtraWhere(where, mColumnQuoted + "=?"), 116 | ProviderUtils.addExtraWhereArgs(whereArgs, parentId)); 117 | } 118 | 119 | @Override 120 | public Cursor queryDir(SQLiteDatabase db, Uri uri, String[] projection, String selection, 121 | String[] selectionArgs, String sortOrder) { 122 | final String parentId = ProviderUtils.getNthPathFromEnd(uri, 1); 123 | 124 | if (WILDCARD_PATH_SEGMENT.equals(parentId)) { 125 | return super.queryDir(db, uri, projection, selection, selectionArgs, 126 | sortOrder != null ? sortOrder : getDefaultSortOrder()); 127 | 128 | } else { 129 | return super.queryDir(db, uri, projection, 130 | ProviderUtils.addExtraWhere(selection, mColumnQuoted + "=?"), 131 | ProviderUtils.addExtraWhereArgs(selectionArgs, parentId), 132 | sortOrder != null ? sortOrder : getDefaultSortOrder()); 133 | } 134 | } 135 | 136 | @Override 137 | public Cursor queryItem(SQLiteDatabase db, Uri uri, String[] projection, String selection, 138 | String[] selectionArgs, String sortOrder) { 139 | final String parentId = ProviderUtils.getNthPathFromEnd(uri, 2); 140 | 141 | if (WILDCARD_PATH_SEGMENT.equals(parentId)) { 142 | return super.queryItem(db, uri, projection, selection, selectionArgs, 143 | sortOrder != null ? sortOrder : getDefaultSortOrder()); 144 | 145 | } else { 146 | return super.queryItem(db, uri, projection, 147 | ProviderUtils.addExtraWhere(selection, mColumnQuoted + "=?"), 148 | ProviderUtils.addExtraWhereArgs(selectionArgs, parentId), 149 | sortOrder != null ? sortOrder : getDefaultSortOrder()); 150 | } 151 | } 152 | 153 | @Override 154 | public void createTables(SQLiteDatabase db) throws SQLGenerationException { 155 | if (!mChild.equals(mParent)) { 156 | super.createTables(db); 157 | } 158 | } 159 | 160 | @Override 161 | public void upgradeTables(SQLiteDatabase db, int oldVersion, int newVersion) { 162 | if (!mChild.equals(mParent)) { 163 | super.upgradeTables(db, oldVersion, newVersion); 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/edu/mit/mobile/android/content/query/QuerystringParser.y: -------------------------------------------------------------------------------- 1 | /* 2 | * A parser to translate from querystring parameters to SQL queries. 3 | * 4 | * Copyright (C) 2012-2013 MIT Mobile Experience Lab 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation version 9 | * 2.1 of the License. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | %language "Java" 22 | %name-prefix "QuerystringParser" 23 | %define package edu.mit.mobile.android.content.query 24 | %define parser_class_name "QuerystringParser" 25 | %define public 26 | /* all tokens will be used as strings. */ 27 | %define stype "String" 28 | %define throws SQLGenerationException 29 | 30 | /* this lets us avoid needing to instantiate the lexer in application code */ 31 | %lex-param {String query} 32 | 33 | /* this parameter lets an input key be aliased to another (possibly qualified) column name */ 34 | %parse-param {HashMap
204 | * Generates a complete MIME type string in the following format: 205 | * {@code vnd.android.cursor.dir/vnd.AUTHORITY.SUFFIX} 206 | *
207 | * 208 | *209 | * SUFFIX is filtered so all invalid characters (see BCP13) are replaced with 211 | * {@link #MIME_INVALID_CHAR_REPLACEMENT}. 212 | *
213 | * 214 | * @param authority 215 | * the authority for this type 216 | * @param suffix 217 | * a raw suffix 218 | * @return the MIME type for the given suffix 219 | */ 220 | public static String toDirType(String authority, String suffix) { 221 | suffix = MIME_INVALID_CHARS.matcher(suffix).replaceAll(MIME_INVALID_CHAR_REPLACEMENT); 222 | return ProviderUtils.TYPE_DIR_PREFIX + authority + "." + suffix; 223 | } 224 | 225 | /** 226 | *227 | * Generates a complete MIME type string in the following format: 228 | * {@code vnd.android.cursor.item/vnd.AUTHORITY.SUFFIX} 229 | *
230 | * 231 | * 232 | *233 | * SUFFIX is filtered so all invalid characters (see BCP13) are replaced with 235 | * {@link #MIME_INVALID_CHAR_REPLACEMENT}. 236 | *
237 | * 238 | * @param authority 239 | * the authority for this type 240 | * @param suffix 241 | * a raw suffix 242 | * @return the MIME type for the given suffix 243 | */ 244 | public static String toItemType(String authority, String suffix) { 245 | suffix = MIME_INVALID_CHARS.matcher(suffix).replaceAll(MIME_INVALID_CHAR_REPLACEMENT); 246 | return ProviderUtils.TYPE_ITEM_PREFIX + authority + "." + suffix; 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/edu/mit/mobile/android/content/DBHelperMapper.java: -------------------------------------------------------------------------------- 1 | package edu.mit.mobile.android.content; 2 | /* 3 | * Copyright (C) 2011-2013 MIT Mobile Experience Lab 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Lesser General Public 7 | * License version 2.1 as published by the Free Software Foundation. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, visit 16 | * http://www.gnu.org/licenses/lgpl.html 17 | */ 18 | 19 | import android.content.ContentProvider; 20 | import android.content.ContentValues; 21 | import android.content.UriMatcher; 22 | import android.database.Cursor; 23 | import android.database.SQLException; 24 | import android.database.sqlite.SQLiteDatabase; 25 | import android.net.Uri; 26 | import android.util.Log; 27 | import android.util.SparseArray; 28 | 29 | /** 30 | * Handles the mapping of a numeric matcher code (usually the code that's used in conjunction with 31 | * {@link UriMatcher}) to a given {@link DBHelper}. 32 | * 33 | * This maintains the state of which verbs are permitted for the given path and only executes a CRUD 34 | * verb if it has permission. Permission can be checked using {@link #canInsert(int)} and friends. 35 | * 36 | */ 37 | public final class DBHelperMapper { 38 | private static final String TAG = DBHelperMapper.class.getSimpleName(); 39 | private final SparseArrayVERB_INSERT | VERB_QUERY
54 | * @param type
55 | * the MIME type of the item to add.
56 | */
57 | public void addDirMapping(int code, DBHelper helper, int verb, String type) {
58 | if (BuildConfig.DEBUG) {
59 | Log.d(TAG, "registered dir mapping of type " + type + " to " + helper);
60 | }
61 | mDbhMap.put(code, new DBHelperMapItem(verb, false, type, helper));
62 | }
63 |
64 | /**
65 | * Makes a mapping from the code to the given DBHelper. This helper will be used to handle any
66 | * queries for items that match the given code. All other items will throw an error. Check
67 | * {@link #canHandle(int)} and {@link #canQuery(int)}, etc. first to ensure that a query will
68 | * complete.
69 | *
70 | * @param code
71 | * A unique ID representing the given URI; usually a {@link UriMatcher} code
72 | * @param helper
73 | * The helper that should be used for this code.
74 | * @param verb
75 | * The SQL verbs that should be handled by the helper. Any other requests will throw
76 | * an error. Verbs can be joined together, eg. VERB_INSERT | VERB_QUERY
77 | * @param type
78 | * the MIME type of the item to add.
79 | */
80 | public void addItemMapping(int code, DBHelper helper, int verb, String type) {
81 | if (BuildConfig.DEBUG) {
82 | Log.d(TAG, "registered item mapping of type " + type + " to " + helper);
83 | }
84 | mDbhMap.put(code, new DBHelperMapItem(verb, true, type, helper));
85 | }
86 |
87 | /**
88 | * @param code
89 | * @return true if this helper has a mapping for the given code
90 | */
91 | public boolean canHandle(int code) {
92 | return mDbhMap.get(code) != null;
93 | }
94 |
95 | /**
96 | * @param code
97 | * @return true if this helper is allowed to insert for the given code
98 | */
99 | public boolean canInsert(int code) {
100 | final DBHelperMapItem item = mDbhMap.get(code);
101 | return item != null && item.allowVerb(VERB_INSERT);
102 | }
103 |
104 | /**
105 | * @param code
106 | * @return true if this helper is allowed to query for the given code
107 | */
108 | public boolean canQuery(int code) {
109 | final DBHelperMapItem item = mDbhMap.get(code);
110 | return item != null && item.allowVerb(VERB_QUERY);
111 | }
112 |
113 | /**
114 | * @param code
115 | * @return true if this helper is allowed to update for the given code
116 | */
117 | public boolean canUpdate(int code) {
118 | final DBHelperMapItem item = mDbhMap.get(code);
119 | return item != null && item.allowVerb(VERB_UPDATE);
120 | }
121 |
122 | /**
123 | * @param code
124 | * @return true if this helper is allowed to delete for the given code
125 | */
126 | public boolean canDelete(int code) {
127 | final DBHelperMapItem item = mDbhMap.get(code);
128 | return item != null && item.allowVerb(VERB_DELETE);
129 | }
130 |
131 | /**
132 | * @param code
133 | * @return the MIME type for the given item.
134 | */
135 | public String getType(int code) {
136 | final DBHelperMapItem dbhmi = mDbhMap.get(code);
137 | if (dbhmi == null) {
138 | throw new IllegalArgumentException("no mapping for code " + code);
139 | }
140 | return dbhmi.type;
141 | }
142 |
143 | private String getVerbDescription(int verb) {
144 | String verbString = null;
145 | if ((verb & VERB_INSERT) != 0) {
146 | verbString = "insert";
147 |
148 | } else if ((verb & VERB_QUERY) != 0) {
149 | verbString = "query";
150 |
151 | } else if ((verb & VERB_UPDATE) != 0) {
152 | verbString = "update";
153 |
154 | } else if ((verb & VERB_DELETE) != 0) {
155 | verbString = "delete";
156 | }
157 | return verbString;
158 | }
159 |
160 | private DBHelperMapItem getMap(int verb, int code) {
161 | final DBHelperMapItem dbhmi = mDbhMap.get(code);
162 |
163 | if (dbhmi == null) {
164 | throw new IllegalArgumentException("No mapping for code " + code);
165 | }
166 | if ((dbhmi.verb & verb) == 0) {
167 | throw new IllegalArgumentException("Cannot " + getVerbDescription(verb) + " for code "
168 | + code);
169 | }
170 | return dbhmi;
171 | }
172 |
173 | public Uri insert(int code, ContentProvider provider, SQLiteDatabase db, Uri uri,
174 | ContentValues values) throws SQLException {
175 | final DBHelperMapItem dbhmi = getMap(VERB_INSERT, code);
176 |
177 | return dbhmi.dbHelper.insertDir(db, provider, uri, values);
178 | }
179 |
180 | public Cursor query(int code, ContentProvider provider, SQLiteDatabase db, Uri uri,
181 | String[] projection, String selection, String[] selectionArgs, String sortOrder) {
182 | final DBHelperMapItem dbhmi = getMap(VERB_QUERY, code);
183 |
184 | if (dbhmi.isItem) {
185 | return dbhmi.dbHelper.queryItem(db, uri, projection, selection, selectionArgs,
186 | sortOrder);
187 | } else {
188 | return dbhmi.dbHelper
189 | .queryDir(db, uri, projection, selection, selectionArgs, sortOrder);
190 | }
191 | }
192 |
193 | public int update(int code, ContentProvider provider, SQLiteDatabase db, Uri uri,
194 | ContentValues cv, String selection, String[] selectionArgs) {
195 | final DBHelperMapItem dbhmi = getMap(VERB_QUERY, code);
196 |
197 | if (dbhmi.isItem) {
198 | return dbhmi.dbHelper.updateItem(db, provider, uri, cv, selection, selectionArgs);
199 | } else {
200 | return dbhmi.dbHelper.updateDir(db, provider, uri, cv, selection, selectionArgs);
201 | }
202 | }
203 |
204 | public int delete(int code, ContentProvider provider, SQLiteDatabase db, Uri uri,
205 | String selection, String[] selectionArgs) {
206 | final DBHelperMapItem dbhmi = getMap(VERB_QUERY, code);
207 |
208 | if (dbhmi.isItem) {
209 | return dbhmi.dbHelper.deleteItem(db, provider, uri, selection, selectionArgs);
210 | } else {
211 | return dbhmi.dbHelper.deleteDir(db, provider, uri, selection, selectionArgs);
212 | }
213 | }
214 |
215 | private class DBHelperMapItem {
216 | public DBHelperMapItem(int verb, boolean isItem, String type, DBHelper dbHelper) {
217 | this.verb = verb;
218 | this.dbHelper = dbHelper;
219 | this.isItem = isItem;
220 | this.type = type;
221 | }
222 |
223 | public boolean allowVerb(int verb) {
224 | return (this.verb & verb) != 0;
225 | }
226 |
227 | final DBHelper dbHelper;
228 | final int verb;
229 | final boolean isItem;
230 | final String type;
231 |
232 | }
233 |
234 | public static final int VERB_INSERT = 1, VERB_QUERY = 2, VERB_UPDATE = 4, VERB_DELETE = 8,
235 | VERB_ALL = VERB_INSERT | VERB_QUERY | VERB_UPDATE | VERB_DELETE;
236 | }
237 |
--------------------------------------------------------------------------------