April 4, 2010

[SOLVED] Intents, sending multily data aka ACTION_SEND_MULTIPLY

FileBrowser Send / Receive Intents




Blackmoon File Browser app has grown from a simple browser to a full fledged file manager and able to launch apps based on the given file name so that you don't have to launch the app and then open the file. Expanding on this idea, FileBrowser grew arms with which to pass off data to other apps in singles or multiples. FileBrowser then grew ears to listen for other apps that wish to use it's functionality to pick files or folders. Most of these Intents are discoverable, but any good dev knows that getting information directly from the source trumps whatever else is available. In this article, I will specify what Intents FileBrowser uses and how you can use them in your own app.

Launch App Intent


The simplest form of Intent used by FileBrowser is the launch intent. There are two methods being used. One specific to "Open File" and the other is used by "Open with...". Open File is used to launch the default activity for the given file's MIME type. Determining a file's MIME type is beyond the scope of this article.
In the example, you want to VIEW the file which has a MIME type of theMIMEtype.
String theMIMEType = getMIMEtype(aFile);
if (theMIMEType!=null) {
Intent theIntent = new Intent(Intent.ACTION_VIEW);
theIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK+Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
theIntent.setDataAndType(Uri.fromFile(aFile), theMIMEType);
try {
startActivity(theIntent);
} catch (ActivityNotFoundException anfe) {
//show error
}
}

Since you declare what the MIME type is supposed to be, any file can try to be opened by any app... most likely it will not, but this allows for opening up an HTML file in a text editor rather than a web browser. If there is more than one app that is capable of handling the launch request for the given MIME type, Android will ask the user to pick one and give them the option of making that choice the default which would prevent Android from asking again in the future.

"Open with..." has a slightly different form of app launcher in that the startActivity() is called with a Chooser rather than merely relying on the default chosen for a particular MIME type. FileBrowser goes an extra step further and instead of merely using the MIME type of the given file, it uses the MIME category instead. A .jpg file with a MIME type of "image/jpeg" would have a MIME category of "image/*". This means that an image viewer that is able to open images, but not necessarily JPEG images will also be included in the list of apps that can receive this file. FileBrowser does this to allow more flexibility in how you can open a file since you sometimes want to use a slightly different app than the default one.
Intent theIntent = new Intent(Intent.ACTION_VIEW);
theIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK+Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
String theMIMEcategory = getMIMEcategory(aFile);
theIntent.setDataAndType(Uri.fromFile(aFile),theMIMEcategory);
try {
startActivity(Intent.createChooser(theIntent,aChooserTitleString));
} catch (Exception e) {
//show error
}

These two VIEW action Intents are the basic examples of how FileBrowser launches an app to open any given file. It launches them as separate apps so that it mimics what a person would actually do on their phone by launching the proper app and then loading that particular file to use with that app. Using FileBrowser merely changes the order of those steps so that you can see what file you wish to use first and then launch the proper app.

Listening for Intents


Listening for Intents is a two step process. The first step being to tell the AndroidManifest.xml which Intents to listen for and what Activity to launch in response to one. A single activity can have more than one Intent filter. FileBrowser's main activity has 3 intents associated with it. The first is the standard main app launcher. A second Intent filter deals specifically with the various pick Intents. The third Intent filter is a specialized pick filter - GET_CONTENT. Here is how FileBrowser defines the later two filters:
<intent-filter>
<action android:name="android.intent.action.PICK" />
<action android:name="org.openintents.action.PICK_FILE" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:scheme="folder" />
<data android:scheme="directory" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.GET_CONTENT" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.OPENABLE" />
<data android:mimeType="*/*" />
</intent-filter>

The first filter states to the Android system that it will accept any action that equals PICK or PICK_FILE. The OR is very important as this only applies to other actions listed in the same filter. We narrow down the PICK actions by specifying that in addtion to either PICK or PICK_FILE, the Intent must also have a data scheme of either "file" or "folder" or "directory". Any combination of Actions and Data Schemes that match that criteria will consider FileBrowser as a potential recipient for the Intent. The categories listed are DEFAULT, which every Intent filter should have, and BROWSABLE, meaning that your Activity is safe to use from a web browser.

The second filter is the GET_CONTENT filter, again listing the DEFAULT category all public filters should have as well as OPENABLE, meaning your activity is able to open the content the Intent is requesting. The data mimeType of "*/*" means that we are capable of opening and returning any MIME type desired. This filter was put into FileBrowser specifically to handle email attaching from the Gmail/Email apps. When Gmail asks Android for an app capable of attaching content to the email, FileBrowser will be listed as one of those apps that can pick a file to attach.

The second step in the process of responding to an Intent is to write the code necessary to send the data back to the requesting app. Since I have already covered how to write such code in an earlier article, I am just going to provide a link to it.

Sending a "Single Datum" Intent


Sending a single file is very similar to opening it. The difference is the Action to use and the Extras needed. The typical recipient for a Send request is email, but any number of apps can also receive such an Intent. I like to think of Send Intents as a kind of throw/catch arrangement as opposed to the View Intent which is more of a "point and look" arrangement.
String theMIMEType = getMIMEtype(aFile);
if (theMIMEType!=null) {
Intent theIntent = new Intent(Intent.ACTION_SEND);
theIntent.setType(theMIMEType);
theIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(aFile));
//next line specific to email attachments
theIntent.putExtra(Intent.EXTRA_SUBJECT, aFile.getName());
try {
startActivity(Intent.createChooser(theIntent, aChooserTitle));
} catch (Exception e) {
//show error
}
}

FileBrowser uses the Send Action, sets the MIME type for the file to be sent, but does not set the Data portion of the Intent. Instead, we add an Extra, specifically an EXTRA_STREAM, with the file Uri as it's value. The EXTRA_SUBJECT is not required, but is an optional feature specific to the email apps that use this Intent to attach files to an email.

Sending/Receiving a "Multiple Data" Intent


This particular feature of FileBrowser is not well defined elsewhere for Android apps. There is no real standard or example to follow and there isn't too many apps that can actually handle multiple files. Hopefully this article will allow more developers to understand and incorporate handling multiple data points (files in our case) into their apps.

Sending multiple files from FileBrowser first requires some mechanism to select mutiple files and organize them somehow. The implementation details of such are not important. Suffice to say, by the time we are ready to send multiple file Uris, we can get an ArrayList from our internal structure. The difficult part is not the packing of the list into the Intent, but actually analyzing each file Uri that is going into it so that the "overall" MIME type for the given list is set as the Intent type.

Sending Multiple


public static String getMIMEcategory(String aMIMEtype) {
if (aMIMEtype!=null) {
aMIMEtype = aMIMEtype.substring(0,aMIMEtype.lastIndexOf("/",aMIMEtype.length()-1))+"/*";
} else {
aMIMEtype = "*/*";
}
return aMIMEtype;
}

protected boolean packageMarkedFiles(Intent aIntent) {
ArrayList theUris = myFilesToSend.toUriList();
//iterate through the list and get overall mimeType
String theOverallMIMEtype = null;
String theMIMEtype = null;
String theOverallMIMEcategory = null;
String theMIMEcategory = null;
Iterator iu = theUris.iterator();
while (iu.hasNext()) {
String theFilename = iu.next().getLastPathSegment();
theMIMEtype = getMIMEtype(theFilename);
if (theOverallMIMEtype!=null) {
if (!theOverallMIMEtype.equals(theMIMEtype)) {
theOverallMIMEcategory = getMIMEcategory(theOverallMIMEtype);
theMIMEcat = getMIMEcategory(theMIMEtype);
if (!theOverallMIMEcat.equals(theMIMEcat)) {
theOverallMIMEtype = "multipart/mixed";
break; //no need to keep looking at the various types
} else {
theOverallMIMEtype = theOverallMIMEcat+"/*";
}
} else {
//nothing to do
}
} else {
theOverallMIMEtype = theMIMEtype;
}
}
if (theUris!=null && theOverallMIMEtype!=null) {
aIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM,theUris);
aIntent.setType(theOverallMIMEtype);
return true;
} else {
return false;
}
}

protected boolean sendMarkedFiles() {
Intent theIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
if (packageMarkedFiles(theIntent,false)) {
startActivity(Intent.createChooser(theIntent, aChooserTitle));
}
return true;
}

As before, determining a file's MIME type is beyond the scope of this article. Once you run through your list of file Uris and determine what the overall MIME type for the list is going to be, filling the Intent is quite simple as it becomes one call to putParcelableArrayListExtra().

Receiving Multiple


Receiving an ACTION_SEND_MULTIPLE Intent is also fairly straightforward since you just call the opposite method to retrive the list of Uris.
Intent theIntent = getIntent();
if (theIntent!=null) {
String theAction = theIntent.getAction();
if (Intent.ACTION_SEND_MULTIPLE.equals(theAction)) {
theFileList = theIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
}
}

Once you have your file Uri list, you can act upon it however you wish. Also, do not forget to put an Intent filter on your activity in the AndroidManifest.xml. The one FileBrowser uses to create playlists from multiple audio files is shown.
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="audio/*" />
</intent-filter>

I encourage other developers to incorporate listening for SEND_MULTIPLE requests into their apps that also listen for single requests. They do not add much more complexity to your app and if you are only listening for them, it is not hard at all to add it as a feature.


Source: FileBrowser Send / Receive Intents.

Another example yo can find on code.google.com

No comments:

Post a Comment