20

The content provider/ resolver APIs provide a complicated, but robust way of transferring data between processes using a URI and the openInputStream() and openOutputStream() methods. Custom content providers have the ability to override the openFile() method with custom code to effectively resolve a URI into a Stream; however, the method signature of openFile() has a ParcelFileDescriptor return type and it is not clear how one might generate a proper representation for dynamically generated content to return from this method.

Returning a memory mapped InputStream from a content provider?

Are there examples of implementing ContentProvider.openFile() method for dynamic content in the existing code base? If not can you suggest source code or process for doing so?

Community
  • 1
  • 1
hannasm
  • 1,951
  • 4
  • 16
  • 25

2 Answers2

28

Check out this great example project from the always helpful CommonsWare. It lets you create a ParcelFileDescriptor pipe with whatever InputStream you want on one side, and the receiving application on the other side:

https://github.com/commonsguy/cw-omnibus/tree/master/ContentProvider/Pipe

The key parts are creating the pipe in openFile:

public ParcelFileDescriptor openFile(Uri uri, String mode)
                                                        throws FileNotFoundException {
    ParcelFileDescriptor[] pipe=null;

    try {
      pipe=ParcelFileDescriptor.createPipe();
      AssetManager assets=getContext().getResources().getAssets();

      new TransferThread(assets.open(uri.getLastPathSegment()),
                       new AutoCloseOutputStream(pipe[1])).start();
    }
    catch (IOException e) {
      Log.e(getClass().getSimpleName(), "Exception opening pipe", e);
      throw new FileNotFoundException("Could not open pipe for: "
          + uri.toString());
    }

    return(pipe[0]);
  }

Then create a thread that keeps the pipe full:

static class TransferThread extends Thread {
    InputStream in;
    OutputStream out;

    TransferThread(InputStream in, OutputStream out) {
        this.in = in;
        this.out = out;
    }

    @Override
    public void run() {
        byte[] buf = new byte[8192];
        int len;

        try {
            while ((len = in.read(buf)) > 0) {
                out.write(buf, 0, len);
            }

            in.close();
            out.flush();
            out.close();
        } catch (IOException e) {
            Log.e(getClass().getSimpleName(),
                    "Exception transferring file", e);
        }
    }
}
  • I use a third party lib that requests files via a content provider and for some reason the data on the other end arrives (and here I'm been careful saying) differently when returning the ParcelFileDescriptor this way or when using a real file like so: ParcelFileDescriptor.open(privateFile, ParcelFileDescriptor.MODE_READ_ONLY) – TacB0sS Apr 10 '15 at 13:39
  • 2
    Any idea why i get an error sometimes when using this provider with bigger amount of requests: java.io.IOException: write failed: EPIPE (Broken pipe) at libcore.io.IoBridge.write(IoBridge.java:502) at java.io.FileOutputStream.write(FileOutputStream.java:186) – Malachiasz Sep 04 '15 at 12:09
  • 2
    I have the same issue as Malachiasz has. Does anybody have any progress in this direction? – isabsent Dec 21 '15 at 18:25
  • Also seeing this error, have you found a workaround after 3 years? – Duncan Watts Dec 11 '18 at 11:47
  • that does indeed work, but its slower than caching the file on filesystem – Luiz Felipe May 15 '20 at 12:39
  • Note that for other use-cases it might be desirable to be able to signal an error from the transfer-thread. In such cases it is possible to use ParcelFileDescriptor.createReliablePipe() instead, and signal error from the writing side by calling ParcelFileDescriptor#closeWithError(). – JohnyTex Nov 08 '21 at 15:32
  • Then detect it on the reading side with ParcelFileDescriptor#checkError(). – JohnyTex Nov 08 '21 at 15:47
2

MemoryFile supports this, but the public API hasn't been finalized.

Jeff Sharkey
  • 2,473
  • 1
  • 17
  • 10
  • 1
    Are there plans to include a conversion between a memoryfile and parcelfiledescriptor in the future? Something along those lines would be nicer than cluttering/polluting the filesystem with temporary files having unkown lifetimes. Maybe there is some way to detect the closing of the stream within the content provider which could offer a little safer way to cleanup after yourself? I am concerned with sending attachments to an (gmail/standaed) email client though I'm sure there are other places where these issues could arise. – hannasm Jan 29 '10 at 03:18
  • 2
    Yes, MemoryFile.java currently has a `public ParcelFileDescriptor getParcelFileDescriptor()` method. This was committed as part of Donut, but as Jeff said, is still not finalized yet. I've confirmed that the "concept" at least works, and can be done currently, using reflection. It's very dirty though, and not recommended :) Unfortunately, even `ParcelFileDescriptor.fromSocket()` can't be used because `Memory.isMemoryFile()` throws an exception because the socket is neither a PFD nor a memory file. – Joe Jul 03 '10 at 09:11
  • 2
    Careful with MemoryFile. If I understand it correctly, it stores the entire contents of a file in memory, so you can't use files bigger than the available memory. – Hans-Christoph Steiner Feb 22 '13 at 19:26