I'm designing an Android application which works with list of complex objects. An object includes textual or graphical data accompanied by a set of attributes (say, level of importance and last viewed time). I'd like to aid my app with copy and paste functionality which would follow 3 rules:
So far, I have studied Google docs and browsed various programming forums but didn't find any answer of how to do this or explanation that it's not feasible. Currently I'm having two evils to choose from:
1) Create a content provider which works with the objects, and copy content URI to the clipboard. Unfortunately, this means that 3rd party applications, in order to retrieve text or an image, must know the internal organization of my content provider, which I surely can't assume.
2) Copy to clipboard just textual data (with type ClipDescription.MIMETYPE_TEXT_PLAIN) or just URI to an image (with type ClipDescription.MIMETYPE_TEXT_URILIST). In such case I can't retain object's attributes when pasting into my own application.
Any ideas?
Using a ContentProvider
is the correct thing to do. Other apps don't need to understand the structure or organization of your content: content URIs can be used as opaque identifiers, and (in API 11 and later) one URI can have multiple types.
If you implement ContentProvider.openTypedAssetFile
, your provider can give an image to clients that request an image, and your internal data format to clients that request your vendor-specific MIME type. This is the mechanism that ClipData.Item.coerceToText
uses to get text from an arbitrary content URI: it requests the type text/*
, and reads the returned stream into a string. (Don't try and copy an infinite stream of text!) It only puts the text of the URI itself if this call fails. The legacy getText
API uses this coerceToText
function too. This sorts out your legacy and text-only clients.
Clients that only want an image should use the same mechanism as above, which means your ContentProvider.openTypedAssetFile
will be used as for text, but with a requested type of image/*
. But I don't think you can rely on every client doing that, so I'd advise that you implement ContentProvider.getType
and ContentProvider.getStreamTypes
to return an image type for that URI (instead of your vendor-specific type).
Implement query
to return a cursor with a column called (the value of) MediaStore.Images.ImageColumns.DATA
. You can also include other columns from MediaStore.Images.ImageColumns
if you want to give metadata to those clients.
Then you just have to implement openFile
to return a file descriptor for the image. Be sure to check the mode
argument to avoid giving other apps the ability to write in your files. You can call ParcelFileDescriptor.open
to create the ParcelFileDescriptor
you need to return from the path you have.
So that's all third-party clients for your service dealt with. Now what about pasting into your own app? There's two things you can do here. As I mentioned in the previous section, you can use ContentResolver.openTypedAssetDescriptorFile
to request your vendor-specific type, and implement ContentProvider.openTypedAssetFile
to return a stream of that type. This is suitable if your app-specific data is a special kind of file containing serialized data or such. If your app-specific content is a database row, then you can use query
: it can put any app-specific data you like into the returned cursor, as well as the MediaStore
columns discussed above.
Either of those methods provides a convenient way to use ContentProvider
to abstract your front-end from your storage mechanism. I did that when implementing sharing in one app, and it was a very positive change, imposing a clear boundary, and forcing me to clean up all my sneaky, encapsulation-violating accesses to the database, resulting in an easier-to-maintain codebase. It also made it easier to use built-in support in Loader
s, Adapter
s, and ContentObserver
s to reduce the size of my front-end code.
But maybe this enforced encapsulation boundary is in the wrong place for your app, or your paste target needs access to the same Java objects, not just a cursor or serialized data. In that case, it's easy enough for the code in the paste target to parse the pasted URI and read the identifier out of it. It can then just talk to your back-end code directly (using whatever existing mechanism you have) to get a reference to the relevant data object: it needn't use ContentResolver
at all.
All of this may seem like a big can of worms to open, and in a way it is. But ContentProvider
is actually pretty easy to implement if you just want to do one thing, and once you've got started with it, you'll probably find other problems it can help you solve.