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();
...
}
}
}

Monday, April 6, 2009

Android's StateListDrawable and RadioButton Example

A radio button on the Android platform is drawn based on a label and a set of images contained in a StateListDrawable. You can create an image radio button by setting the text label to empty and setting your own images. There is no need to override the class or implement your own.

You can create your own radio buttons either via xml declaration or via code. You can see this in the Android source code but I will describe both methods in the rest of this post.

In the Android source code, you want to have a look at the following files:
StateListDrawable.java
DrawableContainer.java

RadioButton.java
CompoundButton.java

btn_radio.xml


XML DECLARATION:
When you declare your radio button, add the tag android:button="@drawable/resize_button". In your res/drawable/ directory, you must now have a resize_button.xml file that looks like this:

<?xml version="1.0" encoding="utf-8"?>

<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_checked="true"
android:state_window_focused="false"
android:drawable="@drawable/resize_button_on" />
<item
android:state_checked="false"
android:state_window_focused="false"
android:drawable="@drawable/resize_button_off" />
<item
android:state_checked="true"
android:state_pressed="true"
android:drawable="@drawable/resize_button_on_pressed" />
<item
android:state_checked="false"
android:state_pressed="true"
android:drawable="@drawable/resize_button_off_pressed" />
<item
android:state_checked="true"
android:state_focused="true"
android:drawable="@drawable/resize_button_on_selected" />
<item
android:state_checked="false"
android:state_focused="true"
android:drawable="@drawable/resize_button_off_selected" />
<item
android:state_checked="true"
android:drawable="@drawable/resize_button_on" />
<item
android:state_checked="false"
android:drawable="@drawable/resize_button_off" />
</selector>


The order of these items seems to matter, but I haven't had the time to delve into the details. It looks like it's caused by the matching algorithm that picks a drawable from the list based on the current state. You'll notice that each image that represents a radio button state has one or more state values associated with it. The radio button class sets the state flags and the Drawable picks the best matching image to display.

By declaring the xml code just shown, I now have a graphical resize radio button that can be checked off. It won't have a label and it will have it's own icon with the usual radio button states.

VIA CODE:

 StateListDrawable drawables = new StateListDrawable();
int stateChecked = android.R.attr.state_checked;
int stateFocused = android.R.attr.state_focused;
int statePressed = android.R.attr.state_pressed;
int stateWindowFocused = android.R.attr.state_window_focused;
drawables.addState(new int[]{ stateChecked, -stateWindowFocused}, getButtonWithThumbnail(R.drawable.button_on , thumbnailBitmap));
drawables.addState(new int[]{-stateChecked, -stateWindowFocused}, getButtonWithThumbnail(R.drawable.button_off , thumbnailBitmap));
drawables.addState(new int[]{ stateChecked, statePressed }, getButtonWithThumbnail(R.drawable.button_on_pressed , thumbnailBitmap));
drawables.addState(new int[]{-stateChecked, statePressed }, getButtonWithThumbnail(R.drawable.button_off_pressed , thumbnailBitmap));
drawables.addState(new int[]{ stateChecked, stateFocused }, getButtonWithThumbnail(R.drawable.button_on_selected , thumbnailBitmap));
drawables.addState(new int[]{-stateChecked, stateFocused }, getButtonWithThumbnail(R.drawable.button_off_selected, thumbnailBitmap));
drawables.addState(new int[]{ stateChecked }, getButtonWithThumbnail(R.drawable.button_on , thumbnailBitmap));
drawables.addState(new int[]{-stateChecked }, getButtonWithThumbnail(R.drawable.button_off , thumbnailBitmap));
thumbnailBitmap.recycle();
thumbnailBitmap = null;
thumbnailButton.setButtonDrawable(drawables);
thumbnailButton.setId(viewId);
layerThumbnails.addView(thumbnailButton);
layerThumbnails.check(thumbnailButton.getId());


The selector tag in the xml file corresponds to the StateListDrawable class. Items are added with addState(). Here, I've copied the basic button drawables from android's jar file, such as R.drawable.button_off and R.drawable.button_off_pressed. I created my own function, getButtonWithThumbnail(), that takes a bitmap and combines it with the basic button image to create a custom radio button on the fly.

Notice that the true and false values of the states are specified in code using positive and negative values of the android.R.attr.state_<*> integer variables and the order in which the images are added to the drawable is the same as in the xml declaration earlier. Finally, I add the StateListDrawable to my radio button, give it an id, add it to a radio group, and simulate a click on the button by calling the check() function on the button's radio group.

Thursday, January 8, 2009

Write Your Cover Letter As If You Are Writing To Your Best Friend

Many job seekers write stiff cover letters and resumes that sound fake, copied, and trite. They read like every other cover letter that has crossed a human resources manager's desk. They all look the same to that manager, and none of them will get noticed. You hear the same advise everywhere: make your cover letter stand out! I couldn't agree more!

My one piece of advise is to write your cover letter as if your best friend is the HR manager who will be reading it!

This technique will ensure that you don't use trite phrases, that you keep it as short as possible, and that you will not embellish your resume and cover letter. After all, your best friend is very familiar with you so you wouldn't be able to lie. A suggestion derived from this idea is that you should address the person by first name only with no last name if you know the name of the cover letter's first reader. This will make you sound friendlier and closer. If you don't know the name of your first reader, you might consider foregoing the "Dear Sir/Madam" salutation, which sounds very distant and uninvited, so that you won't put the reader on the defensive from the first line. So allow me to provide some examples:

- You won't be using words like "furthermore" that create a structured, stiff read. When was the last time you told your best friend "furthermore"?
- You won't use stiff phrases like "I became versed in the dynamics between customers, consultants, developers, and the final product."
- You won't use phrases like "I am painstakingly detail oriented." Sure, I believe you, even though no one else would.
- You won't use half the page to repeat the jobs you've had and the skills you possess, all of which is already on the resume.
- You won't close with phrases like "I believe I have the experience and passion for which you are looking, so I have enclosed my résumé." A simple "I have enclosed my résumé" will do and will get the point across.

Is this technique going to help, hurt, or have no effect? Some companies will find your style too casual while others might find it too formal. This can work to your advantage as a filter for the type of company that will reply to you. Ideally, you know what kind of job you want and you seek it until you find it instead of just applying to anything and everything that sounds plausible and taking the first offer that is good enough. If you know you want to work in a relaxed environment, you should write in a more casual style, ensuring that stiff companies will not bother contacting you. But my main interest in a more casual format is that it will give your cover letter the much coveted ability to stand out. Until everybody starts doing the same, that is.