Android's ContentProvider: how to share a virtual file
Dennis GuseWhile working on OpenTracks, I wanted to send geographical data (e.g., points as well as KML, GPX) to another installed application that could show this kind of data (e.g., OsmAnd or Maps.ME). The main reason for this, was that I did not want this functionality in OpenTracks as it was not a core feature.
To achieve this usually, the FileProvider is used and it’s usage is really simple: just get the Uri of the file to be shared and send an intent (be aware of grantUriPermission). This is awesome as long as the file is already stored on the device - it is painless simple.
However, what happens if the data is not yet stored in the file system?
For example, the data resides in a SQLite database?
This is actually achivable using FileProvider
:
- One could store the data in a temporary file and store it somewhere (e.g., the app’s cache directory).
- Send the intent.
- Later delete the temporary file.
This solution works quite well and is probably in use quite often. However, it does not feel very beautiful (at least not for me).
What I actually wanted was the a ContentProvider
that can share a virtual (i.e., file is created on the fly from database).
Thus, I do not need to create the temporary file and delete it later.
Luckily, this is possible and this is actually quite straight forward.
The following shows an example that is inspired by FileProvider
, but the interesting part is in openFile()
and how a ParcelFileDescriptor
can be created without an actual file.
The full code is available on Github.
In addition, please be aware to make the ShareContentProvider
accessible to the calling app (i.e., grantUriPermission).
For OpenTracks, I ran, however, into another issue related to Android’s security.
OpenTracks uses internally a ContentProvider
that manages access to the internal SQLite database.
My initial approach was to implemented the ShareContentProvider
and let it forward requests to the internal ContentProvider
(i.e., two separate instances), but both belonging to OpenTracks.
In ShareContentProvider
, I used getContext().getContentResolver()
to access the internal ContentProvider
.
This works quite well.
However, if ShareContentProvider
receives a request from another app (access granted via temporary grantUriPermission), Android’s security infrastructure does not allow to forward requests to the internal ContentProvider
.
The reason is rather simple as the calling app only got permission for one URI managed ShareContentProvider
and not for the internal ContentProvider
URIs.
One (im)possible solution would be to make the internal ContentProvder
to be world readable (i.e., exported="true"
), but this would expose all data of your app to all other installed apps.
And this is an absolute no go!
I solved this issue by using only one ContentProvider
for OpenTracks (i.e., merging ShareContentProvider
and the internal ContentProvider
).
The resulting ContentProvider
can then use himself to acquire the necessary data for file sharing URIs.
The implementation is rather simple as ShareContentProvider
inherits from the internal ContentProvider
and forwards all internal requests to it’s parent.
For convenience, ShareContentProvider
has a static function to create a sharing URI as it later needs to parse the URI again.
OpenTracks can now share geo-data files with other map application without using temporary files.