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.
Related
I have two app, from another app I want to get all images,
suppose there is StickerProvider and MainActivity different app
StickerProvider is ContentProvider, MainActivity has ContentResolver
StickerProvider App has Asset Folder
Asset----> Stickers ----------> a.png, b.png
public class StickerProvider extends ContentProvider {
private final static String LOG_TAG = StickerProvider.class.getName();
private static final String[] COLUMNS = {
OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE};
#Override
public boolean onCreate() {
return true;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
/**
* Source: {#link FileProvider#query(Uri, String[], String, String[], String)} .
*/
if (projection == null) {
projection = COLUMNS;
}
String[] images = new String[0];
try {
images = getContext().getAssets().list("stickers");
ArrayList<String> listImages = new ArrayList<String>(Arrays.asList(images));
final MatrixCursor cursor = new MatrixCursor(new String[]{"path"}, 1);
for (int i = 0; i < listImages.size(); i++) {
cursor.addRow(new String[]{listImages.get(i)});
}
return cursor;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
#Override
public String getType(Uri uri) {
/**
* Source: {#link FileProvider#getType(Uri)} .
*/
final String file_name = uri.getLastPathSegment();
final int lastDot = file_name.lastIndexOf('.');
if (lastDot >= 0) {
final String extension = file_name.substring(lastDot + 1);
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mime != null) {
return mime;
}
}
return "image/png";
}
#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) {
return 0;
}
}
It's menifest file is
<provider
android:name="com.example.StickerProvider"
android:authorities="com.example"
android:exported="true"
android:grantUriPermissions="true"
android:label="StickerProvider" />
Another app is MainActivity ------> From this I want to fetch all images of above apps
public class MainActivity extends AppCompatActivity {
int i=0;
private static final String TAG = "MainActivity";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ImageView ivOne = (ImageView)findViewById(R.id.ivOne);
ImageView ivTwo = (ImageView)findViewById(R.id.ivTwo);
try {
ContentResolver resolver = getContentResolver();
Cursor cursor =
resolver.query(Uri.parse("content://com.example/stickers"),
null,
null,
null,
null);
if (cursor.moveToFirst()) {
do {
String word = cursor.getString(0);
if(i==0){
ivOne.setImageURI(Uri.parse("content://com.example/stickers/"+word));
}else if(i==1){
ivTwo.setImageURI(Uri.parse("content://com.example/stickers/"+word));
}
// do something meaningful
Log.d(TAG, "onCreate: "+word);
} while (cursor.moveToNext());
}
}catch(Exception e){
e.printStackTrace();
}
}
}
When ever I start MainActivity App I am getting below exception
Unable to open content: content://com.example/a.png
java.io.FileNotFoundException: No files supported by provider at content://com.example/a.png
at android.database.DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(DatabaseUtils.java:144)
at android.content.ContentProviderProxy.openTypedAssetFile(ContentProviderNative.java:692)
at android.content.ContentResolver.openTypedAssetFileDescriptor(ContentResolver.java:1149)
at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:986)
at android.content.ContentResolver.openInputStream(ContentResolver.java:706)
at android.widget.ImageView.getDrawableFromUri(ImageView.java:900)
at android.widget.ImageView.resolveUri(ImageView.java:871)
at android.widget.ImageView.setImageURI(ImageView.java:490)
at android.support.v7.widget.AppCompatImageView.setImageURI(AppCompatImageView.java:124)
at com.pixr.photo.collage.MainActivity.onCreate(MainActivity.java:36)
at android.app.Activity.performCreate(Activity.java:6684)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1119)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2652)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2766)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1507)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6236)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:891)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:781)
ivOne.setImageURI(Uri.parse("content://com.example/stickers/"+word));
Your ContentProvider does not support this. It does not implement openFile() or any of its variants.
Either:
Use an existing ContentProvider that offers support for serving assets, such as my StreamProvider, or
Augment your provider to support openFile(), such as I do in this sample app
ic_launcher is related with app icon. There is no app icon. Check your icon in your drawable directory.
Welcome all
I have a content provider that must share images from getFilesDir folder. The problem is that the method openFile is never being called. What should i do to achieve it?
I'm using the solution proposed here, but with getFilesDir: Create and Share a File from Internal Storage
i'm sharing the image with this code:
Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND);
theUri = Uri.parse("content://com.myapp.cap/img319058");
shareIntent.setType("image/*");
shareIntent.putExtra(Intent.EXTRA_STREAM,theUri);
SectionManager.getCurrentActivity().startActivity(Intent.createChooser(shareIntent, ""));
The problem is that the method openFile is never being called. Instead of it, it is called the method openAssetFile but i don't want that! i want that the method openFile get's called
This is my content provider:
public class AssetsContentProvider extends ContentProvider{
#Override
public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
return null;
}
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
File root = getContext().getFilesDir();
File cacheDir = getContext().getCacheDir();
File path = new File(root, uri.getEncodedPath());
path.mkdirs();
File file = new File(path, "file_"+uri.getLastPathSegment());
int imode = 0;
if (mode.contains("w")) {
imode |= ParcelFileDescriptor.MODE_WRITE_ONLY;
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
}
if (mode.contains("r"))
imode |= ParcelFileDescriptor.MODE_READ_ONLY;
if (mode.contains("+"))
imode |= ParcelFileDescriptor.MODE_APPEND;
return ParcelFileDescriptor.open(file, imode);
}
#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 false;
}
#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;
}
}
Thanks for your help
I'm currently testing Android's ContentProvider and running into some issues mocking it. I've already spent days on the internet but didn't find any useful examples except of the introduction on the Android Developer Site.
I implemented a ContentProvider and wrote some tests. Everything works here completely fine. The result of the tests are matching my expectations.
public class DirectMessageProviderTest extends ProviderTestCase2<DirectMessageProvider>{
#SmallTest
public void testInsert() {
Log.d(TAG, "testInsert");
/*
* result retrieves twitterId for the newly inserted item
*/
Uri result = provider.insert(
DirectMessageProvider.CONTENT_URI,
createContentValues());
Log.i(TAG, "INSERT; id for newly inserted item: " + result);
assertNotNull("INSERT!!! failed", result);
if (result != null) {
isDirectMessageInserted = true;
}
}
#SmallTest
public void testQuery() {
Log.d(TAG, "testQuery");
Uri uri = Uri.withAppendedPath(
DirectMessageProvider.CONTENT_URI,
String.valueOf(directMessage.getTwitterId()));
/*
* result retrieves a cursor or null
*/
Cursor result = provider.query(
uri,
null,
null,
null,
null);
Log.i(TAG, "QUERY; number of rows inside the cursor: " + result.getCount());
int expected = isDirectMessageInserted ? 1 : 0;
assertEquals("QUERY!!! failed", expected, result.getCount());
}
}
I also implemented a class which capsules the ContentProvider and provides more complex methods than delete, insert, query and update. Take a look!
public class DirectMessageDataAccessImpl implements
DirectMessageDataAccessInterface {
#Override
public boolean isStored(TwitterDirectMessage directMessage)
throws DataAccessException {
Log.d(TAG, "isStored");
try {
Uri uri = Uri.withAppendedPath(
DirectMessageProvider.CONTENT_URI,
String.valueOf(directMessage.getTwitterId()));
Cursor cursor = resolver.query(
uri,
null,
null,
null,
null);
cursor.moveToFirst();
boolean result = (cursor.getCount() > 0 ? true: false);
cursor.close();
return result;
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
throw new DataAccessException(e.getMessage());
}
}
}
Finally we`re coming to my problem writing a test for this class. I want to write independent tests for this class using a mocked ContentResolver. I've found this [example] (http://www.androidadb.com/source/npr-android-app-read-only/Npr_Test/src/org/npr/android/util/PlaylistProviderTest.java.html) on the internet and tried to use MockContentResolver.
First of all, I created a new ContentProvider which will retrieve the delete, insert, query and update calls from the tested class, to create the same answer again and again.
public class DirectMessageDataAccessTest extends ProviderTestCase2<DirectMessageProvider>{
private ContentProvider provider = new ContentProvider() {
#Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
Log.d(TAG, "update");
return 0;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
Log.w(TAG, "query");
return null;
}
#Override
public boolean onCreate() {
Log.d(TAG, "onCreate");
return false;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
Log.d(TAG, "insert");
return null;
}
#Override
public String getType(Uri uri) {
Log.d(TAG, "getType");
return null;
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
Log.d(TAG, "delete");
return 0;
}
};
}
After this, I modified my setUp Method in order to create a MockContentResolver and added my ContentProvider above
#Override
protected void setUp() throws Exception {
super.setUp();
/*
* MockContentResolver overrides Android's normal way of resolving providers by authority
*/
MockContentResolver resolver = new MockContentResolver();
/*
* Adds access to a provider based on its authority
*/
resolver.addProvider(DirectMessageProvider.AUTHORITY, provider);
context = new IsolatedContext(resolver, getContext());
this.setContext(context);
}
Last but not least, I wrote a test case for my isStored Method.
public void testIsStored() {
Log.d(TAG, "testIsStored");
TwitterDirectMessage directMessage = new TwitterDirectMessage();
directMessage.setTwitterId(123456);
DirectMessageDataAccessInterface dataAccess =
new DirectMessageDataAccessImpl(context);
try {
assertFalse(dataAccess.isStored(directMessage));
} catch (DataAccessException e) {
e.printStackTrace();
fail("...");
}
}
Unfortunately, android.content.ContentProvider always throws a NullPointerException. I'm calling resolver.query(...)in line 72 of DirectMessageDataAccessImpl
E/DirectMessageDataAccessImpl(1758): null
E/DirectMessageDataAccessImpl(1758): java.lang.NullPointerException
E/DirectMessageDataAccessImpl(1758): at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:284)
E/DirectMessageDataAccessImpl(1758): at android.content.ContentProvider$Transport.query(ContentProvider.java:177)
E/DirectMessageDataAccessImpl(1758): at android.content.ContentResolver.query(ContentResolver.java:311)
E/DirectMessageDataAccessImpl(1758): at xxx.xxxxx.xxxxxxx.app.database.dataaccess.implementation.DirectMessageDataAccessImpl.isStored(DirectMessageDataAccessImpl.java:72)
E/DirectMessageDataAccessImpl(1758): at xxx.xxxxx.xxxxxxx.app.test.dataaccess.DirectMessageDataAccessTest.testIsStored(DirectMessageDataAccessTest.java:107)
In constructor you should set
public YourClass() {
super(DBProvider.class, "com.yourpackage.main");// class of Your Content provider and application package
}
and then just call
MockContentResolver mockContentResolver = getMockContentResolver();
assertNotNull(mockContentResolver);
in a tests
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 successfully integrated my app's country search into the global search facility and now I am trying to display each country's flag next to the search suggestions. Search inside my app works this way but of course I have control of the list and its view binding myself. So I know the flags are all there and I can use them in the rest of my app.
The trouble comes when I try to supply a Uri to a .gif file in my Assets. According to the search documentation the value of the column with the key SearchManager.SUGGEST_COLUMN_ICON_1 should be a Uri to the image.
Below is what the code looks like. In response to the ContentProvider method public Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) I am creating a MatrixCursor that maps columns from my country database to those required by the search facility. The country names show up fine and I can select them and correctly respond in my application.
I have tried forming the Uri three different ways:
// String flagUri = "file:///android_asset/" + flagPath;
// String flagUri = "file:///assets/" + flagPath;
String flagUri = "android.resource://com.lesliesoftware.worldinfo.WorldInfoActivity/assets/" + flagPath;
columnValues.add (flagUri);
They all lead to the same thing - my application icon next to each suggestion which I can get by using a value of empty string.
Is there a Uri that will work? How can I get the country flag icon next to the search suggestions?
Thanks Ian
The full source:
private Cursor search (String query, int limit) {
query = query.toLowerCase ();
String[] requestedColumns = new String[] {
BaseColumns._ID,
SearchManager.SUGGEST_COLUMN_TEXT_1,
SearchManager.SUGGEST_COLUMN_ICON_1,
};
String[] queryColumns = new String[] {
WorldInfoDatabaseAdapter.KEY_ROWID,
WorldInfoDatabaseAdapter.KEY_COUNTRYNAME,
WorldInfoDatabaseAdapter.KEY_COUNTRYCODE
};
return packageResults (query, requestedColumns, queryColumns, limit);
}
private Cursor packageResults (String query, String[] requestedColumns, String[] queryMappedColumns, int limit) {
if (requestedColumns.length != queryMappedColumns.length)
throw new IllegalArgumentException ("Internal error: requested columns do not map to query columns");
MatrixCursor results = new MatrixCursor (requestedColumns);
// Query the country list returns columns: KEY_ROWID, KEY_COUNTRYNAME, KEY_COUNTRYCODE
Cursor dbResults = myDbHelper.getCountryList (query);
// Verify that the query columns are available
for (int index = 0; index < queryMappedColumns.length; index++) {
int col = dbResults.getColumnIndex (queryMappedColumns[index]);
if (col == -1)
throw new IllegalArgumentException ("Internal error: requested column '" +
queryMappedColumns[index] + "' was not returned from the database.");
}
// Loop over the database results building up the requested results
int rowCount = 0;
while (dbResults.moveToNext () && rowCount < limit) {
Vector<String> columnValues = new Vector<String> ();
for (int index = 0; index < requestedColumns.length; index++) {
if (requestedColumns[index].compareTo (SearchManager.SUGGEST_COLUMN_ICON_1) == 0) {
String flagPath = "flags/small/" + dbResults.getString (
dbResults.getColumnIndexOrThrow (queryMappedColumns[index]))
+ "-flag.gif";
// String flagUri = "file:///android_asset/" + flagPath;
// String flagUri = "file:///assets/" + flagPath;
String flagUri = "android.resource://com.lesliesoftware.worldinfo.WorldInfoActivity/assets/" + flagPath;
columnValues.add (flagUri);
} else {
// Add the mapped query column values from the database
String colValue = dbResults.getString (dbResults.getColumnIndexOrThrow (queryMappedColumns[index]));
columnValues.add (colValue);
}
}
results.addRow (columnValues);
rowCount++;
}
return results;
}
EDIT:
I have tried other variations including moving the images from the assets to the raw folder. Nothing worked. Here are the uri's I tried:
flagUriStr = "android.resource://com.lesliesoftware.worldinfo/raw/flags/small/" +
countryCode + "-flag.gif";
flagUriStr = "android.resource://com.lesliesoftware.worldinfo/assets/flags/small/" +
countryCode + "-flag.gif";
flagUriStr = "android.resource://com.lesliesoftware.worldinfo/assets/flags/small/" +
countryCode + "-flag";
flagUriStr = "android.resource://com.lesliesoftware.worldinfo/raw/" +
countryCode + "-flag.gif";
flagUriStr = "android.resource://com.lesliesoftware.worldinfo/raw/" +
countryCode + "-flag";
The only uri that did work was if I moved a test flag into my drawable folder:
flagUriStr = "android.resource://com.lesliesoftware.worldinfo/" +
R.drawable.small_ca_flag;
Sadly after much searching around I have reached the conclusion that you cannot return a uri to an image asset to the search facility. What I did instead was move my flag images to the resources (so they do not clutter up my app's resources I created a library for the flag images) and use resource uri's. So, in my provider I have code that looks like this in the loop that maps database results to search results:
if (requestedColumns[index].compareTo (SearchManager.SUGGEST_COLUMN_ICON_1) == 0) {
// Translate the country code into a flag icon uri
String countryCode = dbResults.getString (
dbResults.getColumnIndexOrThrow (queryMappedColumns[index]));
int flagImageID = FlagLookup.smallFlagResourceID (countryCode);
String flagUriStr = "android.resource://com.lesliesoftware.worldinfo/" + flagImageID;
columnValues.add (flagUriStr);
}
and look up code that looks like this:
static public int smallFlagResourceID (String countryCode) {
if (countryCode == null || countryCode.length () == 0)
return R.drawable.flag_small_none;
if (countryCode.equalsIgnoreCase ("aa"))
return R.drawable.flag_small_aa;
else if (countryCode.equalsIgnoreCase ("ac"))
return R.drawable.flag_small_ac;
else if (countryCode.equalsIgnoreCase ("ae"))
return R.drawable.flag_small_ae;
...
I will just have to make sure I create a test that verifies that all countries that have flags returns the expected results.
You first create an AssetProvider. We will later create uris that will be handled by this class.
public class AssetsProvider extends ContentProvider {
private AssetManager assetManager;
public static final Uri CONTENT_URI =
Uri.parse("content://com.example.assets");
#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() {
assetManager = 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 = assetManager.openFd(path);
return afd;
} catch (IOException e) {
throw new FileNotFoundException("No asset found: " + uri, e);
}
}
}
Lot's of boilerplate in there. The essential method is of course the openAssetFile that justs translates the path of the uri passed to it into a AssetFileDescriptor. In order for Android to be able to pass URIs to this provider, remember to include this in your AndroidManifest.xml file inside the application-tag:
<provider
android:name=".AssetsProvider"
android:authorities="com.example.assets" />
Now, in your search provider you can create an URI like this:
content://com.example.assets/my_folder_inside_assets/myfile.png
I was able to refer images from drawable in SearchRecentSuggestionsProvider using following uri,
"android.resource://your.package.here/drawable/image_name
Just wanted to add to vidstige's answer: I wanted to serve files that I had downloaded to my temporary cache directory earlier, so I can serve external image thumbnails to the SearchView suggestions from URLs in my search ContentProvider. I used this function instead:
#Override
public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
String filename = uri.getLastPathSegment();
try {
File file = new File(getContext().getCacheDir(), filename);
// image downloading has to be done in the search results provider, since it's not on the UI thread like this guy.
//downloadAndSaveFile("http://urdomain/urfile.png", filename);
AssetFileDescriptor afd = new AssetFileDescriptor(
ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY | ParcelFileDescriptor.MODE_WORLD_READABLE),
0, AssetFileDescriptor.UNKNOWN_LENGTH);
return afd;
} catch (IOException e) {
throw new FileNotFoundException("No asset found: " + uri);
}
}