Question

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:

  • When user copies an object and then pastes it to my app, the full copy of object with the same attributes will be added.
  • When user copies an object and then pastes it to some other app which uses new copy/paste API (android.content.ClipboardManager), that app will receive text or an image depending on whether textual or graphical data the object represents. If it's the image, that app will receive an image in form of file path or media gallery content URI.
  • When user copies an object and then pastes it to some other app which uses old-fashioned deprecated API (android.text.ClipboardManager), that app will receive just text which is represented by the object. If the object represents an image, that app will receive URI in textual representation or even empty text.

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?

Was it helpful?

Solution

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.

Paste as text

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.

Paste as image

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.

Paste as vendor-specific object

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 Loaders, Adapters, and ContentObservers 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.

Conclusion

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top