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 .
Related
Following MediaContentProvider (a simple image file provider) is working correctly with all types of apps, but not with my SMS (or better MMS) app.
I know, the SMS app is expecting a Cursor instead of a ParcelFileDescriptor, does this mean I have to save my image file to a database and retrieve it from there? Or is there a better solution for that?
public class MediaContentProvider extends ContentProvider
{
public static final String AUTHORITY = "MEDIA";
#Override
public int delete(Uri uri, String selection, String[] selectionArgs)
{
return 0;
}
#Override
public String getType(Uri uri)
{
return null;
}
#Override
public Uri insert(Uri uri, ContentValues values)
{
return null;
}
#Override
public boolean onCreate()
{
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 ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException
{
String fileName = uri.getLastPathSegment();
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(getContext().getFilesDir().getAbsolutePath() + "/" + fileName), ParcelFileDescriptor.MODE_READ_ONLY);
return pfd;
}
}
EDIT
as suggested, here my FileProvider... Actually, it results in the same behaviour... As I said, I think I have to somehow provide a cursor for the SMS app...
public class ImageFileProvider extends FileProvider
{
public static final String AUTHORITY = "ImageFileProvider";
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException
{
String fileName = uri.getLastPathSegment();
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(getContext().getFilesDir().getAbsolutePath() + "/" + fileName), ParcelFileDescriptor.MODE_READ_ONLY);
return pfd;
}
}
It may be expecting you to support OpenableColumns in your query() method.
It may be needing you to grant it permission to access the data, as your ContentProvider should be appropriately secured.
It certainly is expecting you to return a real MIME type from getType(), rather than null.
I still recommend FileProvider, but if you want to roll something yourself, this sample app has the basics.
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;
}
For file storage, I'm using a custom ContentProvider cobbled together from a few examples I found online. It seems to be working pretty well as long as I use one file name defined in the class itself (mImageName), but I need to be able to define the file name with a parameter outside of the class itself. Can someone help me with this? Here is a snippet of my ContentProvider class:
public class FileStorageContentProvider extends ContentProvider {
static final String AUTHORITY = "content://com.jwburnside.provider";
public static final Uri CONTENT_URI = Uri.parse(AUTHORITY);
private static final HashMap<String, String> MIME_TYPES = new HashMap<String, String>();
private String mImageName = "myImage";
static {
MIME_TYPES.put(".jpg", "image/jpeg");
MIME_TYPES.put(".jpeg", "image/jpeg");
}
public FileStorageContentProvider() {}
#Override
public boolean onCreate() {
try {
File mFile = new File(getContext().getFilesDir(),mImageName);
if(!mFile.exists()) {
mFile.createNewFile();
getContext().getContentResolver().notifyChange(CONTENT_URI, null);
}
return (true);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
And in my activity:
mImageCaptureUri = FileStorageContentProvider.CONTENT_URI;
You'll usually specify things like that as part of the URI that gets passed to your methods. So you'd use something like
mImageCaptureUri = Uri.withAppendedPath(FileStorageContentProvider.CONTENT_URI, "myImage");
Then inside your various methods (like query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)) you can extract the filename from the URI with uri.getPath().
I am trying to create a button in my android app that allows the user to share an image using their choice of social media network. The image file is stored in the assets folder of the app.
My plan is to implement a custom ContentProvider to give external access to the image, then send a TYPE_SEND intent specifying the uri of the image within my content provider.
I have done this and it works for Google+ and GMail, but for other services it fails. The hardest part has been finding information on what I'm supposed to return from the query() method of my ContentProvider. Some apps specify a projection (e.g. Google+ asks for _id and _data), while some apps pass null as the projection. Even where the projection is specified, I've no idea what actual data (types) are expected in the columns. I can find no documentation on this.
I have also implemented the openAssetFile method of the ContentProvider and this gets called (twice by Google+!) but then inevitably the query method get called as well. Only the result of the query method seems to count.
Any ideas where I'm going wrong? What should I be returning from my query method?
Code below:
// my intent
Intent i = new Intent(android.content.Intent.ACTION_SEND);
i.setType("image/jpeg");
Uri uri = Uri.parse("content://com.me.provider/ic_launcher.jpg");
i.putExtra(Intent.EXTRA_STREAM, uri);
i.putExtra(android.content.Intent.EXTRA_TEXT, text);
startActivity(Intent.createChooser(i, "Share via"));
// my custom content provider
public class ImageProvider extends ContentProvider
{
private AssetManager _assetManager;
public static final Uri CONTENT_URI = Uri.parse("content://com.me.provider");
// not called
#Override
public int delete(Uri arg0, String arg1, String[] arg2)
{
return 0;
}
// not called
#Override
public String getType(Uri uri)
{
return "image/jpeg";
}
// not called
#Override
public Uri insert(Uri uri, ContentValues values)
{
return null;
}
#Override
public boolean onCreate()
{
_assetManager = getContext().getAssets();
return true;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
{
MatrixCursor c = new MatrixCursor(new String[] { "_id", "_data" });
try
{
// just a guess!! works for g+ :/
c.addRow(new Object[] { "ic_launcher.jpg", _assetManager.openFd("ic_launcher.jpg") });
} catch (IOException e)
{
e.printStackTrace();
return null;
}
return c;
}
// not called
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
{
return 0;
}
// not called
#Override
public String[] getStreamTypes(Uri uri, String mimeTypeFilter)
{
return new String[] { "image/jpeg" };
}
// called by most apps
#Override
public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException
{
try
{
AssetFileDescriptor afd = _assetManager.openFd("ic_launcher.jpg");
return afd;
} catch (IOException e)
{
throw new FileNotFoundException("No asset found: " + uri);
}
}
// not called
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException
{
return super.openFile(uri, mode);
}
}
Thanks, your question solved mine ;)
I was having the exactly inverse problem of yours: every service would work except g+.
I was returning null in the query method, that made g+ crash.
The only thing to actually expose my images was to implement openFile().
I have my images stored on the filesystem, not in the assets, but I suppose you
could get a ParcelFileDescriptor from your AssetFileDescriptor and return it.
My openFile() method looks like this:
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
String path = uri.getLastPathSegment();
if (path == null) {
throw new IllegalArgumentException("Not a Path");
}
File f = new File(getContext().getFilesDir() + File.separator + "solved" + path + ".jpg");
int iMode;
if ("r".equals(mode)) {
iMode = ParcelFileDescriptor.MODE_READ_ONLY;
} else if ("rw".equals(mode)) {
iMode = ParcelFileDescriptor.MODE_READ_WRITE;
} else if ("rwt".equals(mode)) {
iMode = ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_TRUNCATE;
} else {
throw new IllegalArgumentException("Invalid mode");
}
return ParcelFileDescriptor.open(f, iMode);
}
This works for every service I have installed with the ACTION_SEND intents except g+.
Using your query method makes it work for google plus, too.
I have some images stored in getExternalFilesDir() and i am trying to show those images in the android gallery (cooliris).
Right now i have been doing this:
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(imgPath,"image/*");
startActivity(intent);
But nothing happens.
I have changed the setDataAndType to this:
intent.setDataAndType(Uri.fromFile(new File(imgPath)),"image/*");
This way it works, but it takes 5-10 seconds for the gallery to go from a black screen to showing my image.
Anyway to solve this or any better approach?
By implementing a file content provider you will be able to avoid this 5-10 second delay
import java.io.File;
import java.io.FileNotFoundException;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
public class FileContentProvider extends ContentProvider {
private static final String AUTHORITY = "content://com.yourprojectinfo.fileprovider";
public static Uri constructUri(String url) {
Uri uri = Uri.parse(url);
return uri.isAbsolute() ? uri : Uri.parse(AUTHORITY + url);
}
public static Uri constructUri(File file) {
Uri uri = Uri.parse(file.getAbsolutePath());
return uri.isAbsolute() ? uri : Uri.parse(AUTHORITY
+ file.getAbsolutePath());
}
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
File file = new File(uri.getPath());
ParcelFileDescriptor parcel = ParcelFileDescriptor.open(file,
ParcelFileDescriptor.MODE_READ_ONLY);
return parcel;
}
#Override
public boolean onCreate() {
return true;
}
#Override
public int delete(Uri uri, String s, String[] as) {
throw new UnsupportedOperationException(
"Not supported by this provider");
}
#Override
public String getType(Uri uri) {
return "image/jpeg";
}
#Override
public Uri insert(Uri uri, ContentValues contentvalues) {
throw new UnsupportedOperationException(
"Not supported by this provider");
}
#Override
public Cursor query(Uri uri, String[] as, String s, String[] as1, String s1) {
throw new UnsupportedOperationException(
"Not supported by this provider");
}
#Override
public int update(Uri uri, ContentValues contentvalues, String s,
String[] as) {
throw new UnsupportedOperationException(
"Not supported by this provider");
}
}
Then you can call
Uri uri = FileContentProvider.constructUri(file);
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
intent.setDataAndType(uri,"image/*");
startActivity(intent);
This is a strange workaround but I think it has something to do with how android opens images using a URI. Their openFile(Uri uri, String mode) method is wrong / broken / can't resolve the URI properly.. I'm not really 100% sure though but I found this workaround to be effective.
don't forget to register to provider in the manifest