Monday, September 26, 2011

Working with Android Cursors from ContentResolver

Ok, so this is just generally a matter of good programing technique, and not necessarily specific to Android, but it's a worth while demonstration of a pattern, and the way I implemented it.  I think it has another name, but I most recently encountered it in Ruby, where they call it "sandwich code."  Terrible name if you ask me.  But then I guess you didn't ask me.

Anyway, so you find yourself doing this same thing over and over again:

        

Cursor cursor = null ; 
 try {
  cursor = getContentResolver().query(uri, ...
  
  while (cursor.moveToNext()) {
   // do something specific here
  }
 } finally {
  if (cursor != null) {
   cursor.close();
  }
 }

 

It's this same bit of code, repeated all over the place in your application, but the only thing that is actually any different is the "do something specific" part.  Would be nice if we didn't have all that repetition, wouldn't it?

If Java had closures, it would be a lot easier. As it is, we can at least write an object oriented solution. To do that, I defined a class called FriendlyContentResolver. Ok, so that's a dumber name than "sandwich code."

Oh well. I was so deeply tempted to extend Android's ContentResolver that I did, in fact, extend ContentResolver...
public class FriendlyContentResolver extends ContentResolver { 

    // no default constructor for ContentResolver, so...
   public FriendlyContentResolver(Context context) {
        super(context);
   }



Whereupon I discovered that this was not such a simple thing to do. When you call the query method you get this exception:

Caused by: java.lang.AbstractMethodError: abstract method not implemented at android.content.ContentResolver.acquireProvider(ContentResolver.java) at android.content.ContentResolver.acquireProvider(ContentResolver.java:748) at android.content.ContentResolver.query(ContentResolver.java:256)

Ok, so I could have spent the time figuring out how to extend ContentResolver, but it probably wasn't worth it. So instead FriendlyContentResolver just delegates to the real ContentResolver, like this...
public class FriendlyContentResolver { 

 public FriendlyContentResolver(ContentResolver realResolver) {
  this.realResolver = realResolver;
 }

    ...

You then provide a version of the query method that takes a reference to a callback interface implementation, like this...
public void query(Uri uri, String[] projection, 
    String selection, 
    String[] selectionArgs, 
    String sortOrder, 
    RowHandler handler) {


Where the RowHandler is defined as...
 

interface RowHandler {
  public void onNextRow(Cursor cursor);
 }

As we'll see in a minute, the RowHandler.onNextRow method will be called once for each row in the result cursor. That's where all the specific work gets done.

RowHandler originally had a method called noResult that would get called if the query returned no results. But I never actually needed it. For all of my cases, it was enough to set a default and simply do nothing when there were no results of the query.

Now we can write the body of the query method that takes care of all of the cursor handling for you and lets you just write the row-handling code...
public void query(Uri uri, String[] projection, 
      String selection, 
      String[] selectionArgs, 
      String sortOrder, 
      RowHandler handler) {

 try {
  cursor = getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
  
  while (cursor.moveToNext()) {
   handler.onNextRow(cursor);
  }
 } finally {
  if (cursor != null) {
   cursor.close();
  }
 }
}

Obviously, you will want to put exception handling in your code, but I wanted to keep the clutter down.

Now, any time you want to make a query with the ContentResolver, all you have to do is this...
FriendlyContentResolver resolver = new FriendlyContentResolver(getContentResolver());
...
final Foo foo = null;

resolver.query(uri, projection, selection, selectionArs, null, 
 new RowHander(){
  @Override
  public void onNextRow(Cursor cursor) {
   String val = cursor.getString(0); // whatever...
   // do whatever you need to do here...
   foo = new Foo(val);
  });


This, of course, assumes the call is being made from an Activity. If it's not, just use the activity to get the ContentResolver instance and pass it along to where you need to instantiate the FriendlyContentResolver.
A potential problem you might encounter with this is when you need to set a value type local variable from inside your anonymous inner class (i.e., your RowHandler instance).

Let's say that foo is an int, rather than a Foo. There are multiple ways to handle this, but they're all basically the same: you create an object reference to hold your primitive type. The easiest way is to shove it in an array, like this...
FriendlyContentResolver resolver = new FriendlyContentResolver(getContentResolver());
...
final int[] foos = {0};

resolver.query(uri, projection, selection, selectionArs, null, 
 new RowHander(){
  @Override
  public void onNextRow(Cursor cursor) {
   String val = cursor.getString(0); // whatever...
   // do whatever you need to do here...
   foos[0] = new Foo(val);
  });

Friday, September 23, 2011

Starting an SMS message via Intent on Android Nexus One

I was looking for a way to start an SMS message via an intent.  There weren't many examples on the web, and the ones there were all looked about like this one:
 
Uri smsUri = Uri.parse("tel:100861");
Intent intent = new Intent(Intent.ACTION_VIEW, smsUri);
intent.putExtra("sms_body", "shenrenkui");
intent.setType("vnd.android-dir/mms-sms"); 
startActivity(intent);

But at least on my phone (Nexus One), this did not work -- at least not fully.

The Message app would start just fine, but it would not pick up the phone number, so you were basically starting from scratch. That defeats the whole purpose of passing the phone number to get you started.

I also saw one example (don't remember where) that provided the URI not in the constructor, as above, but using the setData method, like this:
intent.setData(Uri.parse("tel:" + phoneNumber));
intent.setType("vnd.android-dir/mms-sms");
That can work, but you have to be careful. This code won't work either, on any phone, because, as the documentation for setType says, "This method automatically clears any data that was previously set by setData(Uri)." So you have to set the type FIRST.

So on my Nexus One, if you did this, it would actually open the dialer with that phone number. That's not what I wanted.

Eventually, I focused on the URI and discovered that the problem was the scheme. I changed it to "sms" instead of "tel" and it worked!

So here is the final working code:
Uri uri = Uri.parse("sms:" + phoneNumber);
  Intent intent = new Intent(Intent.ACTION_VIEW);
  intent.setType("vnd.android-dir/mms-sms");
  intent.setData(uri);
    startActivity(intent);

Wednesday, September 7, 2011

Protecting Passwords in Android Applications

Ok, so your Android app needs to collect a user name and password for access to a remote system.  Most likely you have a preferences.xml file like this:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
  xmlns:android="http://schemas.android.com/apk/res/android">
    <EditTextPreference
        android:title="User"
        android:key="userId" android:summary="@string/summary_user"></EditTextPreference>
        
    <EditTextPreference android:title="Password"
        android:key="password" android:password="true"  android:summary="@string/summary_password"/>
        
</PreferenceScreen> 

You dutifully add the android:password="true" attribute in order to have Android treat the value with the proper respect. Then you add your preference Activity, like this
public class FooPreferenceActivity extends PreferenceActivity {

	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		addPreferencesFromResource(R.xml.prefs);
	}
	
}
Works great. But there is a big problem. Take a look at the shared preferences file that Android writes on the device.  Android hasn't really given your password the proper respect.  It's stored in clear text.

Yes, I know, other processes on the device can't normally access the file, but let's not naively assume that such protection really means the password is safe. When your client's lost phone gets rooted, they won't be happy with you if you left their passwords in the clear.

Assuming our package is "com.foo", you can get a look at the preferences with adb like this:

adb pull /data/data/com.foo/shared_prefs/com.foo_preferences.xml

So, how do we get Android to encrypt the password?  Well, maybe there is a way, but I couldn't find it.  And maybe this is a totally stupid newbie trick, but here's how I solved the problem:

I created my own PasswordEditTextPreference for Android to use.  The preferences xml now looks like this:


<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
  xmlns:android="http://schemas.android.com/apk/res/android">
    <EditTextPreference
        android:title="User"
        android:key="userId" android:summary="@string/summary_user"></EditTextPreference>
        
    <PasswordEditTextPreference android:title="Password"
        android:key="password" android:password="true"  android:summary="@string/summary_password"/>
        
</PreferenceScreen> 
Then I created the PasswordEditTextPreference class, which extends EditTextPreference, and overrides methods to hook into the preference handling process and encrypt/decrypt the value in the text view on load/save. Here's what the code looks like:
package android.preference;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;

public class PasswordEditTextPreference extends EditTextPreference {

	private PreferenceObfuscator obfuscator = new PreferenceObfuscator();
	
	public PasswordEditTextPreference(Context context) {
		super(context);
	}

	public PasswordEditTextPreference(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

	public PasswordEditTextPreference(Context context, AttributeSet attrs) {
		super(context, attrs);
	}
	
	@Override
	protected void onDialogClosed(boolean positiveResult) {
		super.getEditText().setText(obfuscator.encrypt(getEditText().getText().toString()));
		super.onDialogClosed(positiveResult);
	}
	
	@Override
	protected void onBindDialogView(View view) {
		super.onBindDialogView(view);
		this.getEditText().setText(obfuscator.decrypt(getEditText().getText().toString()));
	}
}

Notice that this class is in the android.preference package. I don't think this will work if you don't  put it in that package. Also, I have not explored whether all of the constructors are necessary, but  I overrode them to ensure full substitutability with EditTextPreference. The onBindDialogView method decrypts the stored value as it is getting put into the preference edit dialog. The onDialogClosed method then encrypts the value when the user dismisses the dialog.

Strictly speaking, encryption only needs to be done if the "positiveResult" parameter is true. I thought for sure something would complain at me for doing this -- specifically the foreign XML element in the preferences.xml. But it's working great. No more clear-text passwords.