On an Ubuntu phone, apps are1 isolated from one another; each app has its own little folder where its files go, and no other app can intrude. This, obviously, requires some way to exchange files between apps, because frankly there are times when my epub ebook is in my file downloader app and I need it in my ebook reader app. And so on.
To deal with this, Ubuntu provides the Content Hub: a way for an app to say “I need a photo” and all the other apps on your phone which have photos to say “I have photos! Ask me! Me!”.
This is, at a high level, the right thing to do. If my app wants to use a picture of you as an avatar, it should not be able to snarf your whole photo gallery and do what it wants with it. More troubling yet, adding some new social network app should not give it access to your whole address book so that it can hassle your friends to join, or worse just snaffle that information and store it away on its own server for future reference. So when some new app needs a photo of you to be an avatar, it asks the content hub; you, the punter, choose an app to provide that photo, and then a photo from within that app, and our avatar demander gets that photo, and none of the pictures of your kids or your holiday or whatever you take photos of. This is, big picture2 a good idea.
Sadly, the content hub is spectacularly under-documented, so actually using it in your Ubuntu apps is jolly hard work. However, with an assist3 from Michael Zanetti, I now understand how to offer files you have to others via the content hub. So I come to explain this to you.
First, you need permission to access the content hub at all. So, in your appname.apparmor
file4, add content_exchange_source
.5 This tells Ubuntu that you’re prepared to provide files for others (you are a “source” of data). You then need to, also in manifest.json
, configure what you’re allowed to do with the content hub; add a hooks.content-hub
key which names a file (myappname.content-hub
or whatever you prefer). That file that you just named needs to also be json, and looks something like {"source": ["all"]}
, which dictates which sorts of files you want to be a source for.6 Once you’ve done all this, you’re allowed to use the content hub. So now we explore how.
In your QML app, you need to add a ContentPeerPicker
. This is a normal QML Item; specifically, showing it to the user is your responsibility. So you might want to drop it in a Dialog, or a Page, or you might just put it at top level with visible:hidden
and then show it when appropriate (such as when your user taps a file or image or whatever that they want to open in another app).
Your ContentPeerPicker
should look, at minimum, like this:
ContentPeerPicker{handler:ContentHandler.DestinationcontentType:ContentType.AllonPeerSelected:{vartransfer=peer.request();varitems=newArray();exportItem.url=/* whatever the URL of the file you want to share is */;items.push(exportItem);transfer.items=items;transfer.state=ContentTransfer.Charged;cpp.visible=false;}onCancelPressed:cpp.visible=false;}
The important parts here are handler: ContentHandler.Destination
(which means “I am a source for files which need to be opened in some other app”), and contentType: ContentType.All
(which means “I am a source for all types of file”).7 After that8 show it to the user somehow and connect to its onPeerSelected
method. When the user picks some other app to export to from this new Item, onPeerSelected
will be called; when the callback onPeerSelected
is called, the peer
property is valid. Get a transfer object to this peer: var transfer = peer.request();
, and then you need to fill in transfer.items
. This is a JavaScript list of ContentItem
s; specifically, define ContentItem { id: exportItem }
in your app, and then make a “list” of one item with var items = new Array(); exportItem.url = PATH_TO_FILE_YOU_ARE_EXPORTING; items.push(exportItem); transfer.items = items;
.9 After that, set transfer.state = ContentTransfer.Charged
and your transfer begins; you can hide the ContentPeerPicker
by setting cpp.visible=false
at this point.
And that’s how to export files over the Content Hub so that your app can make files available to others. There’s a second half of this (other apps export the files; your app wants to retrieve them, so let’s say they’re an app which needs a photo, and you’re an app with photos), which I’ll come to in a future blog post.
As you can see from the large number of footnotes10 there are a number of caveats with this whole process, in particular that a bunch of it isn’t documented. It will, I’m sure, over time, get better. Meanwhile, the above gives you the basics. Have fun.
- correctly ↩
- ha! ↩
- a bit more than that, if I’m honest ↩
- or whatever you called it;
hooks.$APPNAME.apparmor
inmanifest.json
↩ - This is more confusing than it should be. If you’re using Ubuntu SDK as your editor, then clicking the big “+” button will load a list of possible apparmor permissions. Don’t double-click a permission; this will just show you what it means in code terms, rather irrelevantly. Instead, choose your permission (
content_exchange_source
in this case) and then say Add ↩ - you can also do `{“source”:[“pictures”]}. There may be other things you can write in there instead of “all” or “pictures”, but the documentation is surlily silent on such things. ↩
- You can see all the possible content types in the Ubuntu SDK ContentType documentation (https://developer.ubuntu.com/api/apps/qml/sdk-15.04/Ubuntu.Content.ContentType/), with misleading typos and all ↩
- as mzanetti excellently described it ↩
- You can transfer more than one item, here. ↩
- not this one, though ↩