Two prepackaged databases using sqliteassethelper - android

So I am trying to prepackage a second database using SQLiteAssetHelper and am curious of the correct way to format the provider file. I have already create my DatabaseHelper and all the additional files i need, but need to know how to get the second database created. Currently, my provider file looks as such:
Provider.java
/***
Copyright (c) 2008-2012 CommonsWare, LLC
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.
From _The Busy Coder's Guide to Android Development_
http://commonsware.com/Android
*/
package com.rcd.mypr.Workouts;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.provider.BaseColumns;
import android.text.TextUtils;
public class Provider extends ContentProvider {
private static final int CONSTANTS = 1;
private static final int CONSTANT_ID = 2;
private static final UriMatcher MATCHER;
private static final String TABLE = "constants";
public static final class Constants implements BaseColumns {
public static final Uri CONTENT_URI =
Uri.parse("content://com.commonsware.android.constants.Provider/constants");
public static final String DEFAULT_SORT_ORDER = "title";
public static final String TITLE = "title";
public static final String VALUE = "value";
}
static {
MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
MATCHER.addURI("com.commonsware.android.constants.Provider",
"constants", CONSTANTS);
MATCHER.addURI("com.commonsware.android.constants.Provider",
"constants/#", CONSTANT_ID);
}
private WorkoutsDatabaseHelper db = null;
private TheBenchmarkGirlsDatabaseHelper db2 = null;
#Override
public boolean onCreate() {
db = new WorkoutsDatabaseHelper(getContext());
db2 = new TheBenchmarkGirlsDatabaseHelper(getContext());
return ((db == null) ? false : true);
}
#Override
public Cursor query(Uri url, String[] projection, String selection,
String[] selectionArgs, String sort) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(TABLE);
String orderBy;
if (TextUtils.isEmpty(sort)) {
orderBy = Constants.DEFAULT_SORT_ORDER;
} else {
orderBy = sort;
}
Cursor c =
qb.query(db.getReadableDatabase(), projection, selection,
selectionArgs, null, null, orderBy);
c.setNotificationUri(getContext().getContentResolver(), url);
return (c);
}
#Override
public String getType(Uri url) {
if (isCollectionUri(url)) {
return ("vnd.commonsware.cursor.dir/constant");
}
return ("vnd.commonsware.cursor.item/constant");
}
#Override
public Uri insert(Uri url, ContentValues initialValues) {
long rowID =
db.getWritableDatabase().insert(TABLE, Constants.TITLE,
initialValues);
if (rowID > 0) {
Uri uri =
ContentUris.withAppendedId(Provider.Constants.CONTENT_URI,
rowID);
getContext().getContentResolver().notifyChange(uri, null);
return (uri);
}
throw new SQLException("Failed to insert row into " + url);
}
#Override
public int delete(Uri url, String where, String[] whereArgs) {
int count = db.getWritableDatabase().delete(TABLE, where, whereArgs);
getContext().getContentResolver().notifyChange(url, null);
return (count);
}
#Override
public int update(Uri url, ContentValues values, String where,
String[] whereArgs) {
int count =
db.getWritableDatabase()
.update(TABLE, values, where, whereArgs);
getContext().getContentResolver().notifyChange(url, null);
return (count);
}
private boolean isCollectionUri(Uri url) {
return (MATCHER.match(url) == CONSTANTS);
}
}
I stopped right around modifying the onCreate as I wasn't sure how to have it check if both or either db didnt exist and proceed correctly from there.
Any insight would be appreciated!
Thanks
EDIT: I just got to thinking about it, does it make more sense to just have a second table in the original database, rather than create an entirely new database?
Thanks!

I just got to thinking about it, does it make more sense to just have a second table in the original database, rather than create an entirely new database?
Absolutely. A developer rarely needs more than one SQLite database directly. By "directly", I am specifically not counting the database that might be created by the use of a WebView widget, which happens "under the covers" from the standpoint of your application code.

Are you trying to use one database if the other doesn't exist and need to determine which one is present? Other than that, in my provider, I have have multiple database helpers defined and determine which one to use based on the uri matcher. I just use a bitmask so for example, 100x is dbHelper2 and 200x is dbHelper2. So you could do the following:
private static final int WORKOUT_CONSTANTS = 1001;
private static final int GIRLS_CONSTANTS = 2001;
private DbHelper getDbHelper(Uri uri)
{
int uriCode = MATCHER.match(url);
switch ((int)(uriCode / 1000))
{
case 1:
return db1;
case 2:
return db2;
}
return null;
}

Related

Accessing data from Custom Content Providers

I've two content provider based apps A and B. Both have their own content providers and are setup for reading data from A and B and vice versa. Everything works fine when the other app is in background. But couldn't find another content provider if the app is killed or not present in background. For example, App B wants to read data from App A. 'B' can read data from 'A' successfully when 'A' is running in background, but gave fatal error (Match uri not found) if 'A' is not running in background.
Any thoughts ?
[EDIT]
I'm getting the same issue as this post.
I've this in both apps' manifest:
<provider
android:name="MyContentProvider"
android:authorities="com.example.${applicationId}-provider"
android:enabled="true"
android:exported="true"
android:grantUriPermissions="true">
</provider>
This the error I'm getting:
Writing exception to parcel
java.lang.IllegalArgumentException: Unsupported URI(Query): content://com.example.appA-provider/appA
at com.example.provider.MyContentProvider.query(MyContentProvider.java:142)
at android.content.ContentProvider.query(ContentProvider.java:1007)
at android.content.ContentProvider$Transport.query(ContentProvider.java:218)
at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:112)
at android.os.Binder.execTransact(Binder.java:461)
Note: This only happens when another app is not in background, otherwise it works as expected (can read each other's data fine).
[EDIT 2]
Here's code for MyContentProvider:
package com.example.provider;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
public class MyContentProvider extends ContentProvider {
private static DatabaseHelper dbHelper;
private static final int ALL_ENTRIES = 1;
private static final int SINGLE_ENTRY = 2;
private String mAuthority = BuildConfig.APPLICATION_ID;
private static UriMatcher uriMatcher;
public Uri CONTENT_URI= null;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
}
public MyContentProvider() {}
public void init(String packageName, String authority) {
if (authority == null) {
setAuthority(packageName, true);
} else {
setAuthority(authority, false);
}
uriMatcher.addURI(getAuthority(), TABLE_NAME, ALL_ENTRIES);
uriMatcher.addURI(getAuthority(), TABLE_NAME + "/#", SINGLE_ENTRY);
CONTENT_URI =
Uri.parse("content://" + getAuthority() + "/" + TABLE_NAME);
}
private void setAuthority(String packageName, boolean isPackageName) {
if (isPackageName) {
mAuthority = packageName + ".myprovider";
} else {
mAuthority = packageName;
}
}
public String getAuthority() {
return mAuthority;
}
public Uri getContentUri() {
return CONTENT_URI;
}
#Override
public boolean onCreate() {
dbHelper = new DatabaseHelper(getContext());
return false;
}
//Return the MIME type corresponding to a content URI
#Override
public String getType(Uri uri) {
if (uri == null) {
throw new IllegalArgumentException("Content uri is null: " + uri);
}
if (uriMatcher == null) {
throw new IllegalArgumentException("Unsupported Match URI: " + uri);
}
switch (uriMatcher.match(uri)) {
case ALL_ENTRIES:
return "vnd.android.cursor.dir/vnd." + getAuthority() + "." + TABLE_NAME;
case SINGLE_ENTRY:
return "vnd.android.cursor.item/vnd." + getAuthority() + "." + TABLE_NAME;
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
}
#Override
public Uri insert(Uri uri, ContentValues values) {
Uri _uri = null;
long id = 0;
SQLiteDatabase db = dbHelper.getWritableDatabase();
switch (uriMatcher.match(uri)) {
case ALL_ENTRIES:
case SINGLE_ENTRY:
id = db.insert(TABLE_NAME, null, values);
getContext().getContentResolver().notifyChange(uri, null);
_uri = Uri.parse(CONTENT_URI + "/" + id);
break;
default:
throw new IllegalArgumentException("Unsupported URI (insert): " + uri);
}
return _uri;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
Cursor cursor = null;
String id = null;
switch (uriMatcher.match(uri)) {
case ALL_ENTRIES:
queryBuilder.setTables(TABLE_NAME);
cursor = queryBuilder.query(db, projection, selection,
selectionArgs, null, null, sortOrder);
break;
case SINGLE_ENTRY:
queryBuilder.setTables(TABLE_NAME);
id = uri.getPathSegments().get(1);
if (id != null && !id.isEmpty()) {
queryBuilder.appendWhere(TABLE_NAME + "=" + id);
}
cursor = queryBuilder.query(db, projection, selection,
selectionArgs, null, null, sortOrder);
break;
default:
throw new IllegalArgumentException("Unsupported URI(Query): " + uri);
}
return cursor;
}
}
You can not initialize the content provider from somewhere else in your code like this, as the ContentProvider might be the first (or only) component of your app that's instantiated.
However, you can read the authority dynamically from the Manifest or a String resource. In my answer on Does Android Content Provider authority definition break the DRY rule? I outlined how we that in our OpenTasks-Provider.
I don't see a call to init() in content provider. Is it only called from elsewhere as part of the application's start ?
If so that may explains why the content provider fails when the application is not already started : In this case the UriMatcher is empty and the switch in the query() method falls back to default witch throws the IllegalArgumentException.
You should either call init() in onCreate() or fully initialize the UriMatcher in the static initializer.
You are setting the authority as
private void setAuthority(String packageName, boolean isPackageName) {
if (isPackageName) {
mAuthority = packageName + ".myprovider";
} else {
mAuthority = packageName;
}
}
So your mAuthority is either com.example.provider or com.example.provider.myprovider
However, you have defined authorities in the manifest as
android:authorities="com.example.${applicationId}-provider"
that is com.example.appA-provider

Writing custom Content Provider for photos on phone (part 2)

I'm working on a custom Content Provider for my app. This is part of a course I'm taking on Android apps, so please don't expect the rationale for doing all this to be too great ;-) The whole point here is for me to learn about CP.
I've got a previous post which goes on and on with this, but I think I've managed to simplify my problem quite a bit. So, I'm working on a "gallery app". Since I don't know how and where thumbnail images are stored on the phone, I've decided to simply use MediaStore.Images.Thumbnails to access the thumbs, and show them in my GridView.
However, to fullfill the requirements of said course, I'll write a "PhotoProvider" to load single photos, full screen, in the DetailActivity. Also, for this to make some sense, and not just be a wrapper around MediaStore.Media.Images, I'm extending that data with some EXIF tags from the JPEG file.
Having found a great post here on StackOverflow, and continuing to wrangle the source code provided in class, I've come up with the following classes. Maybe you want to take a look, and help me out (point me in the right direction)?
The URIs I'll support, are context://AUTH/photo/#, to get a specific image (given an IMAGE_ID, from the thumbnail). Furthermore, to make it a bit more interesting, I also want to be able to write data to the EXIF tag UserComment: context://AUTH/photo/#/comment/* (the comment String is the last parameter there). Does that look sensible?
Some questions: (updated)
getType() is confusing. Again, this is lending from the app course. When returning an image, I guess the type should always be image/jpeg (or maybe PNG)?
Edit: Learning more stuff, I now understand that the URI returned from the insert() method is, I guess, optional, but it often useful to return a "link" (i.e. URI) to the new (inserted) data! For my case, after updating the EXIF tags, I could return either null, or an URI to the edited photo: context://AUTH/photo/7271 (where 7271 mimicks the PHOTO_ID).
Below is my (unfinished!) code. Please have a look, especially at the query() and insert() functions :-)
PhotoContract.java
package com.example.android.galleri.app.data;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.net.Uri;
import android.provider.BaseColumns;
import android.provider.MediaStore;
public class PhotoContract {
public static final String CONTENT_AUTHORITY = "no.myapp.android.galleri.app";
public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
public static final String PATH_PHOTO = "photo";
public static final String PATH_COMMENT = "comment";
public static final class PhotoEntry {
public static final Uri CONTENT_URI =
BASE_CONTENT_URI.buildUpon().appendPath(PATH_PHOTO).build();
public static final String COLUMN_DISPLAY_NAME = MediaStore.Images.Media.DISPLAY_NAME;
public static final String COLUMN_DATA = MediaStore.Images.Media.DATA;
public static final String COLUMN_DESC = MediaStore.Images.Media.DESCRIPTION;
public static final String COLUMN_DATE_TAKEN = MediaStore.Images.Media.DATE_TAKEN;
public static final String COLUMN_DATE_ADDED = MediaStore.Images.Media.DATE_ADDED;
public static final String COLUMN_TITLE = MediaStore.Images.Media.TITLE;
public static final String COLUMN_SIZE = MediaStore.Images.Media.SIZE;
public static final String COLUMN_ORIENTATION = MediaStore.Images.Media.ORIENTATION;
public static final String COLUMN_EXIF_COMMENT = "UserComment";
public static final String COLUMN_EXIF_AUTHOR = "Author";
// should these simply be image/png??
public static final String CONTENT_TYPE =
ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_PHOTO;
public static final String CONTENT_ITEM_TYPE =
ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_PHOTO;
// makes an URI to a specific image_id
public static final Uri buildPhotoWithId(Long photo_id) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(photo_id)).build();
}
// since we will "redirect" the URI towards MediaStore, we need to be able to extract IMAGE_ID
public static Long getImageIdFromUri(Uri uri) {
return Long.parseLong(uri.getPathSegments().get(1)); // TODO: is it position 1??
}
// get comment to set in EXIF tag
public static String getCommentFromUri(Uri uri) {
return uri.getPathSegments().get(2); // TODO: is it position 2??
}
}
}
PhotoProvider.java:
package com.example.android.galleri.app.data;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.ExifInterface;
import android.net.Uri;
import android.provider.MediaStore;
import android.support.v4.content.CursorLoader;
import android.util.Log;
import java.io.IOException;
public class PhotoProvider extends ContentProvider {
// The URI Matcher used by this content provider.
private static final UriMatcher sUriMatcher = buildUriMatcher();
static final int PHOTO = 100;
static final int PHOTO_SET_COMMENT = 200;
static UriMatcher buildUriMatcher() {
// 1) The code passed into the constructor represents the code to return for the root
// URI. It's common to use NO_MATCH as the code for this case. Add the constructor below.
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
final String authority = PhotoContract.CONTENT_AUTHORITY;
// 2) Use the addURI function to match each of the types. Use the constants from
// WeatherContract to help define the types to the UriMatcher.
// matches photo/<any number> meaning any photo ID
matcher.addURI(authority, PhotoContract.PATH_PHOTO + "/#", PHOTO);
// matches photo/<photo id>/comment/<any comment>
matcher.addURI(authority, PhotoContract.PATH_PHOTO + "/#/" + PhotoContract.PATH_COMMENT + "/*", PHOTO_SET_COMMENT);
// 3) Return the new matcher!
return matcher;
}
#Override
public String getType(Uri uri) {
// Use the Uri Matcher to determine what kind of URI this is.
final int match = sUriMatcher.match(uri);
switch (match) {
case PHOTO_SET_COMMENT:
return PhotoContract.PhotoEntry.CONTENT_TYPE;
case PHOTO:
return PhotoContract.PhotoEntry.CONTENT_TYPE;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
#Override
public boolean onCreate() {
return true; // enough?
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
MatrixCursor retCursor = new MatrixCursor(projection);
// open the specified image through the MediaStore to get base columns
// then open image file through ExifInterface to get detail columns
Long IMAGE_ID = PhotoContract.PhotoEntry.getImageIdFromUri(uri);
//Uri baseUri = Uri.parse("content://media/external/images/media");
Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
baseUri = Uri.withAppendedPath(baseUri, ""+ IMAGE_ID);
String[] MS_projection = {
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.DATA,
MediaStore.Images.Media.DESCRIPTION,
MediaStore.Images.Media.DATE_TAKEN,
MediaStore.Images.Media.DATE_ADDED,
MediaStore.Images.Media.TITLE,
MediaStore.Images.Media.SIZE,
MediaStore.Images.Media.ORIENTATION};
// http://androidsnippets.com/get-file-path-of-gallery-image
Cursor c = getContext().getContentResolver().query(baseUri, MS_projection, null, null, null);
// dump fields (the ones we want -- assuming for now we want ALL fields, in SAME ORDER) into MatrixCursor
Object[] row = new Object[projection.length];
row[0] = c.getString(0); // DISPLAY_NAME
row[1] = c.getBlob(1); // etc
row[2] = c.getString(2);
row[3] = c.getLong(3);
row[4] = c.getLong(4);
row[5] = c.getString(5);
row[6] = c.getInt(6);
row[7] = c.getInt(7);
// NB! Extra +2 fields, for EXIF data.
try {
ExifInterface exif = new ExifInterface((String)row[1]);
row[8] = exif.getAttribute("UserComment");
row[9] = exif.getAttribute("Author");
} catch (IOException e) {
e.printStackTrace();
}
retCursor.addRow(row);
return retCursor;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
String comment_to_set = PhotoContract.PhotoEntry.getCommentFromUri(uri);
Long IMAGE_ID = PhotoContract.PhotoEntry.getImageIdFromUri(uri);
Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
baseUri = Uri.withAppendedPath(baseUri, ""+ IMAGE_ID);
// get DATA (path/filename) from MediaStore -- only need that specific piece of information
String[] MS_projection = {MediaStore.Images.Media.DATA};
// http://androidsnippets.com/get-file-path-of-gallery-image
Cursor c = getContext().getContentResolver().query(baseUri, MS_projection, null, null, null);
String thumbData = c.getString(0);
try {
ExifInterface exif = new ExifInterface(thumbData);
exif.setAttribute("UserComment", comment_to_set);
exif.saveAttributes();
} catch (IOException e) {
e.printStackTrace();
}
return PhotoContract.PhotoEntry.buildPhotoWithId(IMAGE_ID); // return URI to this specific image
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}
I believe I managed to figure this out, and it wasn't so mysterious after all. Writing a custom content, in this case, provider really boiled down to implementing my own query and update methods. I didn't need any insert or delete, since I'm only reading the MediaStore.
To get started, I designed two URIs; one for query and one for update. The query URI followed the pattern, content://AUTH/photo/#, where # is the IMAGE_ID from MediaStore.Images.Thumbnails. This method essentially runs another query against MediaStore.Images.Media, to get the "standard" data on the specific image, but then gets some additional meta data on the image via the ExifInterface. All this is returned in a MatrixCursor.
The update URI matches the pattern, content://AUTH/photo/#/comment, which then sets (writes) the UserComment EXIF tag in the file with that ID -- again via the ExifInterface.
To answer my question, I'm posting updated code from my PhotoContract and PhotoProvider classes. Essentially, if you're writing a content provider which does not interface the SQLite database -- but something else on the device -- all you need to do is implement those operations in the appropriate methods in your provider class.
PhotoContract
import android.content.ContentResolver;
import android.content.ContentUris;
import android.net.Uri;
import android.provider.BaseColumns;
import android.provider.MediaStore;
public class PhotoContract {
public static final String CONTENT_AUTHORITY = "com.example.android.myFunkyApp.app";
public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
public static final String PATH_PHOTO = "photo";
public static final String PATH_COMMENT = "comment";
//public static final String PATH_AUTHOR = "author";
public static final class ThumbEntry {
public static final String COLUMN_THUMB_ID = MediaStore.Images.Thumbnails._ID;
public static final String COLUMN_DATA = MediaStore.Images.Thumbnails.DATA;
public static final String COLUMN_IMAGE_ID = MediaStore.Images.Thumbnails.IMAGE_ID;
}
public static final class PhotoEntry {
public static final Uri CONTENT_URI =
BASE_CONTENT_URI.buildUpon().appendPath(PATH_PHOTO).build();
public static final String COLUMN_IMAGE_ID = MediaStore.Images.Media._ID;
public static final String COLUMN_DISPLAY_NAME = MediaStore.Images.Media.DISPLAY_NAME;
public static final String COLUMN_DATA = MediaStore.Images.Media.DATA;
public static final String COLUMN_DESC = MediaStore.Images.Media.DESCRIPTION;
public static final String COLUMN_DATE_TAKEN = MediaStore.Images.Media.DATE_TAKEN;
public static final String COLUMN_DATE_ADDED = MediaStore.Images.Media.DATE_ADDED;
public static final String COLUMN_TITLE = MediaStore.Images.Media.TITLE;
public static final String COLUMN_SIZE = MediaStore.Images.Media.SIZE;
public static final String COLUMN_ORIENTATION = MediaStore.Images.Media.ORIENTATION;
public static final String COLUMN_EXIF_COMMENT = "UserComment";
//public static final String COLUMN_EXIF_AUTHOR = "Author";
public static final String CONTENT_TYPE =
ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_PHOTO;
public static final String CONTENT_ITEM_TYPE =
ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_PHOTO;
// makes an URI to a specific image_id
public static final Uri buildPhotoUriWithId(Long photo_id) {
return CONTENT_URI.buildUpon().appendPath(Long.toString(photo_id)).build();
}
// since we will "redirect" the URI towards MediaStore, we need to be able to extract IMAGE_ID
public static Long getImageIdFromUri(Uri uri) {
return Long.parseLong(uri.getPathSegments().get(1)); // always in position 1
}
}
}
PhotoProvider
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.AbstractWindowedCursor;
import android.database.Cursor;
import android.database.CursorWindow;
import android.database.CursorWrapper;
import android.database.MatrixCursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.ExifInterface;
import android.net.Uri;
import android.provider.MediaStore;
import android.support.v4.content.CursorLoader;
import android.util.Log;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
public class PhotoProvider extends ContentProvider {
// The URI Matcher used by this content provider.
private static final UriMatcher sUriMatcher = buildUriMatcher();
static final int PHOTO = 100;
static final int PHOTO_COMMENT = 101;
static final int PHOTO_AUTHOR = 102;
static UriMatcher buildUriMatcher() {
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
final String authority = PhotoContract.CONTENT_AUTHORITY;
// matches photo/<any number> meaning any photo ID
matcher.addURI(authority, PhotoContract.PATH_PHOTO + "/#", PHOTO);
// matches photo/<photo id>/comment/ (comment text in ContentValues)
matcher.addURI(authority, PhotoContract.PATH_PHOTO + "/#/" + PhotoContract.PATH_COMMENT, PHOTO_COMMENT);
// matches photo/<photo id>/author/ (author name in ContentValues)
//matcher.addURI(authority, PhotoContract.PATH_PHOTO + "/#/" + PhotoContract.PATH_AUTHOR, PHOTO_AUTHOR);
return matcher;
}
#Override
public String getType(Uri uri) {
// Use the Uri Matcher to determine what kind of URI this is.
final int match = sUriMatcher.match(uri);
// Note: We always return single row of data, so content-type is always "a dir"
switch (match) {
case PHOTO_COMMENT:
return PhotoContract.PhotoEntry.CONTENT_TYPE;
case PHOTO_AUTHOR:
return PhotoContract.PhotoEntry.CONTENT_TYPE;
case PHOTO:
return PhotoContract.PhotoEntry.CONTENT_TYPE;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
#Override
public boolean onCreate() {
return true;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
MatrixCursor retCursor = new MatrixCursor(projection);
// open the specified image through the MediaStore to get base columns
// then open image file through ExifInterface to get detail columns
Long IMAGE_ID = PhotoContract.PhotoEntry.getImageIdFromUri(uri);
Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
baseUri = Uri.withAppendedPath(baseUri, ""+ IMAGE_ID);
// http://androidsnippets.com/get-file-path-of-gallery-image
// run query against MediaStore, projection = null means "get all fields"
Cursor c = getContext().getContentResolver().query(baseUri, null, null, null, null);
if (!c.moveToFirst()) {
return null;
}
// match returned fields against projection, and copy into row[]
Object[] row = new Object[projection.length];
int i = 0;
/* // Cursor.getType() Requires API level > 10...
for (String colName : projection) {
int idx = c.getColumnIndex(colName);
if (idx <= 0) return null; // ERROR
int colType = c.getType(idx);
switch (colType) {
case Cursor.FIELD_TYPE_INTEGER: {
row[i++] = c.getLong(idx);
break;
}
case Cursor.FIELD_TYPE_FLOAT: {
row[i++] = c.getFloat(idx);
break;
}
case Cursor.FIELD_TYPE_STRING: {
row[i++] = c.getString(idx);
break;
}
case Cursor.FIELD_TYPE_BLOB: {
row[i++] = c.getBlob(idx);
break;
}
}
}
*/
//http://stackoverflow.com/questions/11658239/cursor-gettype-for-api-level-11
CursorWrapper cw = (CursorWrapper)c;
Class<?> cursorWrapper = CursorWrapper.class;
Field mCursor = null;
try {
mCursor = cursorWrapper.getDeclaredField("mCursor");
mCursor.setAccessible(true);
AbstractWindowedCursor abstractWindowedCursor = (AbstractWindowedCursor)mCursor.get(cw);
CursorWindow cursorWindow = abstractWindowedCursor.getWindow();
int pos = abstractWindowedCursor.getPosition();
// NB! Expect resulting cursor to contain data in same order as projection!
for (String colName : projection) {
int idx = c.getColumnIndex(colName);
// simple solution: If column name NOT FOUND in MediaStore, assume it's an EXIF tag
// and skip
if (idx >= 0) {
if (cursorWindow.isNull(pos, idx)) {
//Cursor.FIELD_TYPE_NULL
row[i++] = null;
} else if (cursorWindow.isLong(pos, idx)) {
//Cursor.FIELD_TYPE_INTEGER
row[i++] = c.getLong(idx);
} else if (cursorWindow.isFloat(pos, idx)) {
//Cursor.FIELD_TYPE_FLOAT
row[i++] = c.getFloat(idx);
} else if (cursorWindow.isString(pos, idx)) {
//Cursor.FIELD_TYPE_STRING
row[i++] = c.getString(idx);
} else if (cursorWindow.isBlob(pos, idx)) {
//Cursor.FIELD_TYPE_BLOB
row[i++] = c.getBlob(idx);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
// have now handled the first i fields in projection. If there are any more, we expect
// these to be valid EXIF tags. Should obviously make this more robust...
try {
ExifInterface exif = new ExifInterface((String) row[2]);
while (i < projection.length) {
row[i] = exif.getAttribute("UserComment"); //projection[i]); // String (or null)
i++;
}
} catch (IOException e) {
e.printStackTrace();
}
retCursor.addRow(row);
return retCursor;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
// URI identifies IMAGE_ID and which EXIF tag to set; content://AUTH/photo/<image_id>/comment or /author
// first, get IMAGE_ID and prepare URI (to get file path from MediaStore)
Long IMAGE_ID = PhotoContract.PhotoEntry.getImageIdFromUri(uri);
Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
baseUri = Uri.withAppendedPath(baseUri, ""+ IMAGE_ID);
// get DATA (path/filename) from MediaStore -- only need that specific piece of information
String[] MS_projection = {MediaStore.Images.Media.DATA};
// http://androidsnippets.com/get-file-path-of-gallery-image
Cursor c = getContext().getContentResolver().query(baseUri, MS_projection, null, null, null);
if (!c.moveToFirst()) return -1; // can't get image path/filename...
String thumbData = c.getString(0);
// then, use URIMatcher to identify the "operation" (i.e., which tag to set)
final int match = sUriMatcher.match(uri);
String EXIF_tag;
switch (match) {
case PHOTO_COMMENT: {
EXIF_tag = "UserComment";
break;
}
case PHOTO_AUTHOR: {
EXIF_tag = "Author";
break;
}
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
if (EXIF_tag == null) return -1;
// finally, set tag in image
try {
ExifInterface exif = new ExifInterface(thumbData);
String textToSet = values.get(EXIF_tag).toString();
if (textToSet == null)
throw new IOException("Can't retrieve text to set from ContentValues, on key = " + EXIF_tag);
if (textToSet.length() > 48)
throw new IOException("Data too long (" + textToSet.length() + "), on key = " + EXIF_tag);
exif.setAttribute(EXIF_tag, textToSet);
exif.saveAttributes();
} catch (IOException e) {
e.printStackTrace();
}
return 1; // 1 image updated
}
}

ContentResolver.delete deletes all entries from my ContentProvider

I have created an app in which I use a ContentProvider I created to keep track of some photos my app creates.
The concept is simple.
I capture some Images and save the Image name, date, and path to a ContentProvider.
And then I use this content provider to load each time thumbnails etc from this ContentProvider when my app starts etc.
I use a CursorAdapter and ListActivity for the full picture.
If the user deletes the image from a file manager or through my app, I want to delete that entry from the ContentProvider.
I have this method which is supposed to do so.
private void deletePhotoFromContentProvider(String path){
String[] selectionArgs=new String[]{path};
Log.i(TAG, "Path to delete:" + path);
String whereClause=""+PhotosContract.PHOTO_FILE_PATH+"=?";
int res = mContext.getContentResolver().delete(PhotosContract.CONTENT_URI,
whereClause, selectionArgs);
Log.i(TAG, "Deleted :" + Integer.toString(res));
mContext.getContentResolver().notifyChange(PhotosContract.CONTENT_URI, null);
}
My table has an ID, a photo_name, photo_date, photo_file_path.
I perform the delete based on the file_path which is basically unique.
I checked the path and the Log prints the correct one, but when i try to delete this deletes all my entries. Note that the
ContentValues values = initValues();
mContext.getContentResolver().insert(PhotosContract.CONTENT_URI, values);
works properly.
This is also my PhotosContract class
public class PhotosContract {
public static final String AUTHORITY = "com.android.testapp.provider";
public static final Uri BASE_URI = Uri
.parse("content://" + AUTHORITY + "/");
public static final String PHOTOS_TABLE_NAME = "photos";
// The URI for this table.
public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_URI,
PHOTOS_TABLE_NAME);
public static final String _ID = "_id";
public static final String PHOTO_FILE_PATH = "photo_file_path";
public static final String PHOTO_FILE_NAME = "photo_name";
public static final String PHOTO_DATE = "photo_date";
}
I also tried to use something like this:
String whereClause=""+PhotosContract.PHOTO_FILE_PATH+"=" + path;
int res = mContext.getContentResolver().delete(PhotosContract.CONTENT_URI,
whereClause, null);
but does not work either. It also deletes all my ContentProvider entries.
Any help?
Thanks in advance.
EDIT:
I am posting my delete method from my ContentProvider:
#Override
public int delete(Uri arg0, String arg1, String[] arg2) {
int rowsDeleted = mDbHelper.getWritableDatabase().delete(
PhotosContract.PHOTOS_TABLE_NAME, arg1, arg2);
getContext().getContentResolver().notifyChange(
PhotosContract.CONTENT_URI, null);
return rowsDeleted;
}

Android setImageUri not working with asset Uri

I am using the following code to set an image from the assets folder.
Uri numBgUri = Uri.parse("file:///android_asset/background_numbers.png");
numBgImage.setImageURI(numBgUri);
The background_numbers.png file definitely exists in the assets root directory. I am getting a FileNotFoundException in the log: -
09-23 17:05:23.803: WARN/ImageView(23713): Unable to open content: file:///android_asset/background_numbers.png
Any ideas what I could be doing wrong?
Thanks!
I managed to get a viable workaround for this using my own custom ContentProvider (It's easier than you think!). Thanks to skink on Google Groups for the tip
This code should work, you just need to set a provider URL and register it as a provider in the manifest file
package sirocco.widgets;
import java.io.FileNotFoundException;
import java.io.IOException;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.database.Cursor;
import android.net.Uri;
public class AssetContentProvider extends ContentProvider {
private AssetManager mAssetManager;
public static final Uri CONTENT_URI =
Uri.parse("content://your.provider.name");
#Override
public int delete(Uri arg0, String arg1, String[] arg2) {
return 0;
}
#Override
public String getType(Uri uri) {
return null;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
#Override
public boolean onCreate() {
mAssetManager = getContext().getAssets();
return true;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
#Override
public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
String path = uri.getPath().substring(1);
try {
AssetFileDescriptor afd = mAssetManager.openFd(path);
return afd;
} catch (IOException e) {
throw new FileNotFoundException("No asset found: " + uri);
}
}
}
I hope this helps any googlers out there with the same problem!
You need to place all images in you resources directory. For example :
/root/res/drawable/background_numbers.png
And you can access it via:
numBgImage.setImageResource(R.drawable.background_numbers);
I advice you to double check that:
ImageView activity that appears in your logcat belongs to the same apk that your code. asset files are only readable from within your application.
The assets folder is in the project directory root.
EDIT
It seems that it is not possible to feed an ImageView directly from an asset ressource. See this question for illustration.
As a workaround you should try to create a drawable from your asset file, then associate it with your Image view using ImageView.setImageDrawable:
Drawable d = Drawable.createFromStream(getAssets().open("background_numbers.png"), null);
if (null != d) numBgImage.setImageDrawable(d);
The first line of this code is an adaptation of this answer from #IgorKhomenko .

How to use QuickSearchBox in my Android application?

I want to implement a search method in my application. I have a database, I want to search in that database using a quick search box. Please help me to achieve this task.
Three files are relevant, DataProvider, DataIndex, changes to the AndroidManifest.
In my case the data objects I want to look for in my database are 'Locations' data objects, hence the name of my classes, but you can apply it for your logic without problem.
LocationProvider.java:
package com.myapp.android.search;
import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import com.myapp.android.MyApp;
import com.myapp.android.model.Location;
import java.util.ArrayList;
/**
* Provides search suggestions for a list of words and their definitions.
*/
public class LocationProvider extends ContentProvider {
public static String AUTHORITY = "myapp_locations";
private static final int SEARCH_SUGGEST = 0;
private static final int SHORTCUT_REFRESH = 1;
private static final UriMatcher sURIMatcher = buildUriMatcher();
/**
* The columns we'll include in our search suggestions. There are others that could be used
* to further customize the suggestions, see the docs in {#link SearchManager} for the details
* on additional columns that are supported.
*/
private static final String[] COLUMNS = {
"_id", // must include this column
SearchManager.SUGGEST_COLUMN_TEXT_1,
SearchManager.SUGGEST_COLUMN_TEXT_2,
SearchManager.SUGGEST_COLUMN_INTENT_DATA,
};
/**
* Sets up a uri matcher for search suggestion and shortcut refresh queries.
*/
private static UriMatcher buildUriMatcher() {
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, SEARCH_SUGGEST);
matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH_SUGGEST);
matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_SHORTCUT, SHORTCUT_REFRESH);
matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_SHORTCUT + "/*", SHORTCUT_REFRESH);
return matcher;
}
#Override
public boolean onCreate() {
Resources resources = getContext().getResources();
// LocationIndex.getInstance(this.getContext()).ensureLoaded(resources);
return true;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
if (!TextUtils.isEmpty(selection)) {
throw new IllegalArgumentException("selection not allowed for " + uri);
}
if (selectionArgs != null && selectionArgs.length != 0) {
throw new IllegalArgumentException("selectionArgs not allowed for " + uri);
}
if (!TextUtils.isEmpty(sortOrder)) {
throw new IllegalArgumentException("sortOrder not allowed for " + uri);
}
switch (sURIMatcher.match(uri)) {
case SEARCH_SUGGEST:
String query = null;
if (uri.getPathSegments().size() > 1) {
query = uri.getLastPathSegment().toLowerCase();
}
return getSuggestions(query, projection);
case SHORTCUT_REFRESH:
String shortcutId = null;
if (uri.getPathSegments().size() > 1) {
shortcutId = uri.getLastPathSegment();
}
return refreshShortcut(shortcutId, projection);
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
}
private Cursor getSuggestions(String query, String[] projection) {
String processedQuery = query == null ? "" : query.toLowerCase();
ArrayList<Location> words = LocationIndex.getInstance().getMatches(processedQuery);
MatrixCursor cursor = new MatrixCursor(COLUMNS);
long id = 0;
for (Location word : words) {
cursor.addRow(columnValuesOfWord(id++, word));
}
return cursor;
}
private Object[] columnValuesOfWord(long id, Location loc) {
return new Object[] {
id, // _id
loc.getTitle(), // text1
loc.getDescription(), // text2
loc.getTitle(), // intent_data (included when clicking on item)
};
}
/**
* Note: this is unused as is, but if we included
* {#link SearchManager#SUGGEST_COLUMN_SHORTCUT_ID} as a column in our results, we
* could expect to receive refresh queries on this uri for the id provided, in which case we
* would return a cursor with a single item representing the refreshed suggestion data.
*/
private Cursor refreshShortcut(String shortcutId, String[] projection) {
return null;
}
/**
* All queries for this provider are for the search suggestion and shortcut refresh mime type.
*/
public String getType(Uri uri) {
switch (sURIMatcher.match(uri)) {
case SEARCH_SUGGEST:
return SearchManager.SUGGEST_MIME_TYPE;
case SHORTCUT_REFRESH:
return SearchManager.SHORTCUT_MIME_TYPE;
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
}
public Uri insert(Uri uri, ContentValues values) {
throw new UnsupportedOperationException();
}
public int delete(Uri uri, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException();
}
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException();
}
}
LocationIndex.java
package com.myapp.android.search;
import com.myapp.android.model.Location;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
public class LocationIndex {
private boolean mLoaded = false;
private static final LocationIndex sInstance = new LocationIndex();
private ArrayList<Location> locations;
private final ConcurrentHashMap<String, ArrayList<Location>> mDict = new ConcurrentHashMap<String, ArrayList<Location>>();
public static LocationIndex getInstance() {
return sInstance;
}
public synchronized void loadWords(ArrayList<Location> locations) { //throws IOException, Resources resources
if (mLoaded) return;
this.locations = locations;
for (Iterator<Location> iter=locations.iterator();iter.hasNext();) {
Location loc = iter.next();
if (loc!=null) addLocation(loc);
}
mLoaded = true;
}
public ArrayList<Location> getMatches(String query) {
ArrayList<Location> list = mDict.get(query);
return list == null ? new ArrayList<Location>() : list;
}
private void addLocation(Location loc) {
final int len = loc.getTitle().length();
for (int i = 0; i < len; i++) {
final String prefix = loc.getTitle().substring(0, len - i);
addMatch(prefix, loc);
}
}
private void addMatch(String query, Location loc) {
ArrayList<Location> matches = mDict.get(query);
if (matches == null) {
matches = new ArrayList<Location>();
mDict.put(query.toLowerCase(), matches);
}
matches.add(loc);
}
public ConcurrentHashMap<String, ArrayList<Location>> getmDict() {
return mDict;
}
}
AndroidManifest.xml
add the following to your manifest...
<!-- Provides search suggestions for words and their definitions. -->
<provider android:name=".search.LocationProvider"
android:authorities="myapp_locations"
android:syncable="false"/>
<provider android:name=".content.LocalFileContentProvider"
android:authorities="com.myapp.android.localfile"
android:syncable="false"/>
and the intent filter in the activities you want to activate the search for (maybe all):
<intent-filter>
<action android:name="android.intent.action.SEARCH"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>

Categories

Resources