Customize "share picture via" menu on Android (Android Intent Filters) eggie5.com
Customize "share picture via" menu on Android (Android Intent Filters)
Alex Egg, 2010-11-13 2011-06-14You know in the android gallery when you view a picture there is the share menu that allows you to upload your picture to various services. It is the job of the app to handle the upload - for example on my phone (depending on the apps you have installed) facebook, picasa, blogger all have entries in that list. This article will describe the process to get your app in that list and post the picture over HTTP.
Basically there are 3 steps to get this working:
- Register your app w/ the android platform to show your app in the platform share menu. (see Intents)
- Get the image from the Album app using android APIs (see ContentResolver)
- Do actual HTTP post request
1. Add hook to AndroidManifest.xml
If you intend to get your app in the share menu you must register that intent (pun intended) w/ the andoid system. This is done via and intent-filter in the
AndroidManifest.xml
file.In order to use the internet to make and HTTP request we must also register that w/ Android via the use-permission directive in the manifest file. See below example:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.eggie5" android:versionCode="1" android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".post_to_eggie5" android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.INTERNET" />
</manifest>2. Get image from Gallery
Getting an image from the camera isn't as straight forward as it seems. It's not just open the file at this path - you must use the ContentResolver/ContentProvider API in android - which the gallery app exposes. See code example:
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Intent intent = getIntent();
Bundle extras = intent.getExtras();
String action = intent.getAction();
// if this is from the share menu
if (Intent.ACTION_SEND.equals(action))
{
if (extras.containsKey(Intent.EXTRA_STREAM))
{
try
{
// Get resource path from intent callee
Uri uri = (Uri) extras.getParcelable(Intent.EXTRA_STREAM);
// Query gallery for camera picture via
// Android ContentResolver interface
ContentResolver cr = getContentResolver();
InputStream is = cr.openInputStream(uri);
// Get binary bytes for encode
byte[] data = getBytesFromFile(is);
// base 64 encode for text transmission (HTTP)
byte[] encoded_data = Base64.encodeBase64(data);
String data_string = new String(encoded_data); // convert to string
SendRequest(data_string);
return;
} catch (Exception e)
{
Log.e(this.getClass().getName(), e.toString());
}
} else if (extras.containsKey(Intent.EXTRA_TEXT))
{
return;
}
}
}First we get a path to the image (
uri
) from the share menu of the gallery when we clicked on share. It's not however just a path to the image - it's a contenturi
which is android platform specific way to refrence a resource. It looks somehting like thiscontent://com.example.gallery/3
. With a URI you can then query gallery for the resource. The key here isgetContentResolver()
method - it will get you a handle to android internal DRM system which the cameria/gallery exposes(uses). Then in order to sent the image over HTTP we must convert it to plaintext encoding (base64) from binary. We do this by gettings it's bits and converting to base64 string representation (details are beyond the scope of this article - see source for details).3. HTTP Post Request
Rather than choose a high level library for the HTTP action I chose to implement it using sockets as an exercise (more fun - we can really see what's happening).
First we build the xml post body string. We then create the request and send it. Note: most people would do this using multipart/mime but I choose just to encode the image into the xml photo node.
private void SendRequest(String data_string)
{
try
{
String xmldata = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<photo><photo>" + data_string
+ "</photo><caption>via android - " + new Date().toString()
+ "</caption></photo>";
// Create socket
String hostname = "eggie5.com";
String path = "/photos";
int port = 80;
InetAddress addr = InetAddress.getByName(hostname);
Socket sock = new Socket(addr, port);
// Send header
BufferedWriter wr = new BufferedWriter(new OutputStreamWriter(
sock.getOutputStream(), "UTF-8"));
wr.write("POST " + path + " HTTP/1.1\r\n");
wr.write("Host: eggie5.com\r\n");
wr.write("Content-Length: " + xmldata.length() + "\r\n");
wr.write("Content-Type: text/xml; charset=\"utf-8\"\r\n");
wr.write("Accept: text/xml\r\n");
wr.write("\r\n");
// Send data
wr.write(xmldata);
wr.flush();
// Response
BufferedReader rd = new BufferedReader(new InputStreamReader(
sock.getInputStream()));
String line;
while ((line = rd.readLine()) != null)
{
Log.v(this.getClass().getName(), line);
}
} catch (Exception e)
{
Log.e(this.getClass().getName(), "Upload failed", e);
}
}Then on the server side - I must decode the base64 stream back into binary which I did in ruby:
Base64.decode64(incoming_file)
See full source on github: https://github.com/eggie5/android-share-menu
Next Steps:
When the image is uploading the UI kinda locks up - the next step would be to have some type of progress bar like the facebook or picasa app does.
Also usually the images are way to big for the front page of this site (where they go) - should implement some type of resize on the phone. This would also make the upload faster.