janoliver
janoliver

Reputation: 7824

Android ContentProvider Cursor issue

I am trying to write a content provider for my application that allows access to some files that my app stores on the SD Card. The files' names are hashes of their content URI, so for example content://my.app.files/test.pdf would sit at /sdcard/data/Android/my.app/cache/MD5(my.app.files/test.pdf) where MD5() is the MD5 hash.

My content provider looks like this:

public class CacheContentProvider extends ContentProvider {
    private static final String[] COLUMNS= { OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE };
    public static final Uri CONTENT_URI = Uri.parse("content://my.app.files/");
    private DiskCache mCache;

    @Override
    public boolean onCreate() {
        PotDroidApplication.initImageLoader(getContext());
        final ImageLoader il = ImageLoader.getInstance();
        mCache = il.getDiskCache();
        return true;
    }

    @Override
    public String getType(Uri uri) {
        final File file = getFileForUri(uri);

        final int lastDot = file.getName().lastIndexOf('.');
        if (lastDot >= 0) {
            final String extension = file.getName().substring(lastDot + 1);
            final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
            if (mime != null) {
                return mime;
            }
        }

        return "application/octet-stream";
    }

    @Override
    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
        final File f = getFileForUri(getContentUriFromUrlOrUri(uri.toString()));

        if (f.exists()) {
            return ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
        }

        throw new FileNotFoundException(uri.getPath());
    }

    private File getFileForUri(Uri uri) {
        return DiskCacheUtils.findInCache(uri.toString(), mCache);
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        final File file = getFileForUri(uri);

        if (projection == null) {
            projection = COLUMNS;
        }

        String[] cols = new String[projection.length];
        Object[] values = new Object[projection.length];
        int i = 0;
        for (String col : projection) {
            if (OpenableColumns.DISPLAY_NAME.equals(col)) {
                cols[i] = OpenableColumns.DISPLAY_NAME;
                values[i++] = uri.getLastPathSegment();
            } else if (OpenableColumns.SIZE.equals(col)) {
                cols[i] = OpenableColumns.SIZE;
                values[i++] = file.length();
            }
        }
        cols = copyOf(cols, i);
        values = copyOf(values, i);
        final MatrixCursor cursor = new MatrixCursor(cols, 1);
        cursor.addRow(values);
        return cursor;
    }

    @Override
    public Uri insert(Uri uri, ContentValues initialValues) {
        throw new RuntimeException("Operation not supported");
    }

    @Override
    public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
        throw new RuntimeException("Operation not supported");
    }

    @Override
    public int delete(Uri uri, String where, String[] whereArgs) {
        throw new RuntimeException("Operation not supported");
    }

    public static Uri getContentUriFromUrlOrUri(String rawUrl) {
        if(rawUrl.startsWith(CONTENT_URI.toString()))
            return Uri.parse(rawUrl);
        try {
            URL url = new URL(rawUrl.replace("%20","+"));
            return Uri.parse(CONTENT_URI + url.getHost() + url.getPath());
        } catch (MalformedURLException e) {
            e.printStackTrace();
            return null;
        }
    }

    private static String[] copyOf(String[] original, int newLength) {
        final String[] result = new String[newLength];
        System.arraycopy(original, 0, result, 0, newLength);
        return result;
    }

    private static Object[] copyOf(Object[] original, int newLength) {
        final Object[] result = new Object[newLength];
        System.arraycopy(original, 0, result, 0, newLength);
        return result;
    }

    public static class HashFileNameGenerator extends Md5FileNameGenerator {
        @Override
        public String generate(String url) {
            Uri uri = getContentUriFromUrlOrUri(url);
            if(uri == null)
                return super.generate(url);
            return super.generate(uri.toString());
        }
    }

}

The Provider seems to work fine resolving the content:// URIs and sharing with certain apps, e.g., Dropbox, works fine as well. However, some Apps throw Exceptions when I try to share my files with them, for example the Acrobat PDF converter. The exception is the following:

Caused by: java.lang.IllegalStateException: Couldn't read row 0, col -1 from CursorWindow.  Make sure the Cursor is initialized correctly before accessing data from it.
            at android.database.CursorWindow.nativeGetString(Native Method)
            at android.database.CursorWindow.getString(CursorWindow.java:434)
            at android.database.AbstractWindowedCursor.getString(AbstractWindowedCursor.java:51)
...

So, apparently, the app is looking for some column that is not present in the Cursor I return via the query() method. What am I doing wrong? I basically copied the query() method from Android's native FileProvider.

Upvotes: 1

Views: 568

Answers (1)

pskink
pskink

Reputation: 24750

if you are exposing media files in your ContentProvider then its query() method should return MediaStore.MediaColumns

Upvotes: 2

Related Questions