5
votes

Trying to convert a URI that I got from Intent.ACTION_GET_CONTENT, input stream opens fine for local files, but for URI from drive (/document/acc=1;doc=4089) I get a FileNotFoundException, saying the file is "virtual". How can I open an input stream for such files?

Getting the URI:

 Intent i = new Intent(Intent.ACTION_GET_CONTENT);
 i.setType("*/*"); //No I18N
 try{
    startActivityForResult(Intent.createChooser(i, "Pick a file"), REQUEST_CODE_UPLOAD_FILE_FOR_IMPORT);
    }catch (android.content.ActivityNotFoundException ex){
    Log.e("Error","FileManager not found!"); 
    }

and

importedFileUri = data.getData();
                System.out.println("URI path: "+importedFileUri.getPath()+" "+importedFileUri.getEncodedPath());
                System.out.println("URI Scheme "+importedFileUri.getScheme());
                System.out.println("URI Authority :"+importedFileUri.getAuthority());
                System.out.println("URI Fragment :"+importedFileUri.getFragment());
                System.out.println("URI path segments : ");
                for(String str : importedFileUri.getPathSegments()){
                    System.out.println("\t" +str );
                }
                String ext;
                if (importedFileUri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
                    final MimeTypeMap mime = MimeTypeMap.getSingleton();
                    ext = mime.getExtensionFromMimeType(mActivity.getContentResolver().getType(importedFileUri));
                    System.out.println("resolved type (content) : "+ mActivity.getContentResolver().getType(importedFileUri));
                } else {
                    ext = MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(new File(importedFileUri.getPath())).toString());
                    System.out.println("resolved type (other) : "+ Uri.fromFile(new File(importedFileUri.getPath())).toString());
                }

Getting the inputstream:

InputStream is = null;
                try {
                    is = contentResolver.openInputStream(importedFileUri);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }

Result:

URI path: /document/acc=1;doc=4089 /document/acc%3D1%3Bdoc%3D4089

GetString - content://com.google.android.apps.docs.storage/document/acc%3D1%3Bdoc%3D4089

URI Scheme content

URI Authority :com.google.android.apps.docs.storage

URI Fragment :null

URI path segments :

document

acc=1;doc=4089

resolved type (content) : application/vnd.google-apps.spreadsheet

ext : null

The Exception:

java.io.FileNotFoundException: File is virtual: acc=1;doc=4089

2
Yes of course. Please show your code. - greenapps
but for URI from drive (/document/acc=1;doc=4089) I That is no uri. No content scheme. Tell the complete scheme please. - greenapps
@greenapps this is my code and my result - ColonD
You should als print importedFileUri.toString(). As that is the real content scheme. - greenapps
Trying to convert a URI. I do not see that you are trying to change the uri. That is not needed of course. - greenapps

2 Answers

6
votes

Check if File at URI is Virtual or not:

private static boolean isVirtualFile(Uri uri) {
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
       if (!DocumentsContract.isDocumentUri(activity, uri)) {
           return false;
       }

       Cursor cursor = activity.getContentResolver().query(
            uri,
            new String[]{DocumentsContract.Document.COLUMN_FLAGS},
            null, null, null);
       int flags = 0;
       if (cursor.moveToFirst()) {
           flags = cursor.getInt(0);
       }
       cursor.close();
       return (flags & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT) != 0;
   } else {
       return false;
   }
}

Get Input stream from Virtual file with this:

private static InputStream getInputStreamForVirtualFile(Uri uri, String mimeTypeFilter)
            throws IOException {

        ContentResolver resolver = activity.getContentResolver();

        String[] openableMimeTypes = resolver.getStreamTypes(uri, mimeTypeFilter);

        if (openableMimeTypes == null ||
                openableMimeTypes.length < 1) {
            throw new FileNotFoundException();
        }

        return resolver
                .openTypedAssetFileDescriptor(uri, openableMimeTypes[0], null)
                .createInputStream();
    }

Overall

if (isVirtualFile(sourceuri)) {
   input = getInputStreamForVirtualFile(sourceuri, getMimeType(name));
} else {
   input = activity.getContentResolver().openInputStream(sourceuri);
}

More information here.

2
votes

Since an app cannot directly open a virtual file by using the openInputStream() method, your app does not receive any virtual files if you include the CATEGORY_OPENABLE category in the ACTION_OPEN_DOCUMENT intent.

To open virtual files, your client app needs to include special logic to handle them. If you want to get a byte representation of the file—to preview the file, for example—you need to request for an alternate MIME type from the documents provider.

To get a URI for a virtual document in your app, first you create an Intent to open the file picker UI, like the code shown previously in Seach for documents.

private boolean isVirtualFile(Uri uri) {
    if (!DocumentsContract.isDocumentUri(this, uri)) {
        return false;
    }

    Cursor cursor = getContentResolver().query(
        uri,
        new String[] { DocumentsContract.Document.COLUMN_FLAGS },
        null, null, null);

    int flags = 0;
    if (cursor.moveToFirst()) {
        flags = cursor.getInt(0);
    }
    cursor.close();

    return (flags & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT) != 0;
}

After you verify that the file is virtual, you can then coerce the file into an alternative MIME type such as an image file. The following code snippet shows how to check whether a virtual file can be represented as an image, and if so, gets an input stream from the virtual file.

private InputStream getInputStreamForVirtualFile(Uri uri, String mimeTypeFilter)
    throws IOException {

    ContentResolver resolver = getContentResolver();

    String[] openableMimeTypes = resolver.getStreamTypes(uri, mimeTypeFilter);

    if (openableMimeTypes == null ||
        openableMimeTypes.length &lt; 1) {
        throw new FileNotFoundException();
    }

    return resolver
        .openTypedAssetFileDescriptor(uri, openableMimeTypes[0], null)
        .createInputStream();
}

The description and code snippet are taken from the android docs. Check out the link and reference to learn more about Storage Access Framework.

Youtube

Reference