6

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;
Ian Leslie
  • 841
  • 1
  • 9
  • 25

4 Answers4

7

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.

Ian Leslie
  • 841
  • 1
  • 9
  • 25
5

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
vidstige
  • 12,492
  • 9
  • 66
  • 110
  • 1
    Thanks, this worked for me! There were a few gotchas (like I think CONTENT_URI *has* to be there? Was having trouble when it wasn't.) – phreakhead Mar 27 '13 at 19:35
  • yeah, the CONTENT_URI needs to be there, it will be read by Andoid and matched against the `android:authorities` attribute in the manifest. – vidstige May 29 '15 at 09:05
  • Hey! Great answer! I'm having some trouble due to the size of the asset image thats been loaded. I understand thats this is due to the fact that images loaded from assets folder do not have the "scaling" feature based on screen density that images loaded from the resources folder have. Any ideas on how to work around this problem? – acrespo Sep 08 '16 at 22:09
  • @acrespo yes, you'r correct, this will not select different files, etc, depending on the resolution. The UI will however scale the images if that is ok for you. If you need more control of per-pixel-appearance and/or performance I suggest using drawables instead. You cold probably also add this logic in the `openAssetFile` function. – vidstige Sep 09 '16 at 07:39
  • @vidstige how would you do that? I thought about handling the image size in the `openAssetFile` function but I don't know how to work with `AssetFileDescriptor`. I don't think that it cane be achieved :s – acrespo Sep 09 '16 at 19:39
1

I was able to refer images from drawable in SearchRecentSuggestionsProvider using following uri,

"android.resource://your.package.here/drawable/image_name
Sachin
  • 31
  • 2
0

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);
        }
    }
phreakhead
  • 14,721
  • 5
  • 39
  • 40