Tuesday, April 7, 2009

Android's Image ContentProvider and Directories

One of the things to which I didn't find and easy answer on Google is how to work with the Images ContentProvider per directory. It's easy to reference all images the ContentProvider knows, but here's how to work with images in a specific directory.

Saving an image file and updating the ContentProvider
When you save an image file to the SD card, it will not automatically show in the 'Pictures' app. To have it appear there, you have to notify the ContentProvider that is responsible for images that a new image file has been created. Here's how it's done:


private void downloadImage(String imageUrl) throws InterruptedException {
InputStream inputStream = null;
OutputStream outStream = null;
try {
URL url = new URL(imageUrl);
inputStream = url.openStream();
String filepath = getFilePath(imageUrl);
String filename = getFileName(imageUrl);
File imageDirectory = new File(filepath);
File file = new File(filepath + filename);
if (file.exists() == false) {
String path = imageDirectory.toString().toLowerCase();
String name = imageDirectory.getName().toLowerCase();
ContentValues values = new ContentValues(7);
values.put(Images.Media.TITLE, filename);
values.put(Images.Media.DISPLAY_NAME, filename);
values.put(Images.Media.DATE_TAKEN, new Date().getTime());
values.put(Images.Media.MIME_TYPE, "image/jpeg");
values.put(Images.ImageColumns.BUCKET_ID, path.hashCode());
values.put(Images.ImageColumns.BUCKET_DISPLAY_NAME, name);
values.put("_data", filepath + filename);
ContentResolver contentResolver = getApplicationContext().getContentResolver();
Uri uri = contentResolver.insert(Images.Media.EXTERNAL_CONTENT_URI, values);
outStream = contentResolver.openOutputStream(uri);
byte[] buffer = new byte[1024];
int count;
while ((count = inputStream.read(buffer)) != -1) {
if (Thread.interrupted() == true) {
String functionName = Thread.currentThread().getStackTrace()[2].getMethodName() + "()";
throw new InterruptedException("The function " + functionName + " was interrupted.");
}
outStream.write(buffer, 0, count);
}
}
}
catch (IOException e) {
}
finally {
if (inputStream != null) {
try {
inputStream.close();
}
catch (IOException e) {
}
}
if (outStream != null) {
try {
outStream.close();
}
catch (IOException e) {
}
}
}
}


The magic piece here is that the ContentProvider is aware of each image file's directory through the BUCKET_ID field. We create a File object that points to the desired directory and we populate the BUCKET_ID with the hashCode() value of the path returned by the toString() function of the File object. Also note, that you have to drop this path to lowercase before obtaining the hashCode().

Choosing an image from one directory
If in your app, you need the user to pick an image from a directory, you can launch the 'Pictures' app to provide that functionality for you. You can start the 'Pictures' app using an 'android.intent.action.PICK' Intent. This will easily let you choose an image from all images across the SD card, but if you need to limit it to a single directory, you again have to work with the BUCKET_ID. Here's the code:

final int CHOOSE_AN_IMAGE_REQUEST = 2910;
String directory = "/sdcard/someDirectory/";
Uri uri = Images.Media.INTERNAL_CONTENT_URI.buildUpon().appendQueryParameter("bucketId", getBucketId(directory)).build();
Intent intent = getIntent();
intent.setData(uri);
intent.setAction(Intent.ACTION_PICK);
startActivityForResult(Intent.createChooser(intent, "Choose a Viewer"), CHOOSE_AN_IMAGE_REQUEST);


public static String getBucketId(String bucketName) {
bucketName = bucketName.toLowerCase();
if (bucketName.charAt(bucketName.length() - 1) == '/') {
bucketName = bucketName.substring(0, bucketName.length() - 1);
}
return Integer.toString(bucketName.hashCode());
}


Once the user chooses an image, your Activity's onActivityResult() function will be called. You can get the chosen image's uri in the following way:


protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
if (requestCode == CHOOSE_AN_IMAGE_REQUEST) {
Uri chosenImageUri = data.getData();
...
}
}
}

5 comments:

Anonymous said...
This comment has been removed by a blog administrator.
Nick said...

Really useful info, thanks for the posts. Turns out I was doing it all wrong.

Jin said...
This comment has been removed by the author.
Jin said...

Thanks it is really helpful.

Peter Knego said...

Or you can simply instruct MediaScanner to do the whole work for you: http://stackoverflow.com/questions/6482751/sd-card-in-android