Sunday, December 28, 2008

50,000

On Friday, a week after I released it "Save MMS" passed the 50,000 download mark. Not bad for such a small app. Unfortunately I was too sick to enjoy it. I got a stomach virus and spent Friday and most of Saturday weak and in terrible pain.

The world has a balance apparently.

Sunday, December 21, 2008

Name change

I decided to change the name of my blog from mylifeonandroid.blogspot.com to andrewoid.blogspot.com because there is already a blog called mylifewithandroid.blogspot.com

Closed API, Open System.

The "SMS problem" on Android currently is that you can send and receive MMS messages, but there is no interface to save what you receive to the sd card. This is a very laughable oversight by the dev team for version 1. Something that I can't believe is still a problem months after launch.

I fully expected the Android Market community to solve this problem eventually. However there was one main obstacle in their way.

If you looked at the Android API reference you won't see any mention of MMS messages. Even if you search for MMS you find 4 results, none of which show you in code how to access them. SMS is a little better it. There is at least code to send SMS messages. But the whole thing is limited in scope.

However, applications such as MyBackup and Txtract are able to access text messages. In the case of MyBackup it is also able to backup MMS messages. It's a good program. They both are. And when I looked at them I was amazed to see that while they were able to get "underneath" the API and access data the API never intended neither of them thought to solve the "SMS Problem". MyBackup gets close. It saves the attachments but doesn't do it in a way that allows the phone to then access it easily.

So I decided I was going to tackle the "SMS Problem".

My first thought was the the SmsManager class had some hidden functionality that was missing from the API. No such luck.

I poked around the Android Source for a while. This yielded a ton of information, none of it distinct on how to solve the problem. I happened upon a post somewhere online which gave code that used a ContentResolver to query a http://code.google.com/android/reference/android/net/Uri.html, to return a Cursor object that could then be used to access the messages. Ah ha!

Wait a minute what does all that mean?

URI stands for Uniform Resource Identifier (Go read that first link I dare ya). They are strings in a certain format that define a resource. A URLs, that address at the top of web browsers are a class of URIs.

A Cursor is the object used to access data (like that stored in a database) which is organized in columns and rows.

And a ContentResolver is a "class (that) provides applications access to the content model."

In short. You make a database query, using a string to find the data that isn't talked about anywhere in the android reference.

How that's done is talked about here.

So armed with this knowledge, the "special" URI, I was able to create "Save MMS". And solve the "SMS Problem" I estimate at least 2 months ahead of cupcake being pushed out to T-Mobile customers.

Is this good or bad? I'm not really sure. There was no documentation for what I was doing. I had to drudge through it and even when I thought I had something working some users are still encountering problems. I'm assuming this is due to different phone sending MMS with different metadata. I simply did not have enough information to write clean enough code for this.

Thursday, December 18, 2008

Reflection cont.

In a previous post I laid out a scheme for using reflection to turn GPS on and off and update the providers.



That didn't work in the end because of this error.

...
W/System.err( 415): Caused by: java.lang.SecurityException: Permission Denial: getIntentSender() from pid=415, uid=10023 is not allowed to send as package android
...

So how do you tell android to update the providers? Well you use a communication mechanism called an Intent.

I'm not sold on this tech idea. And android is built on it. To update the providers it just takes 2 lines of code.

Intent intent = new Intent( Intent.ACTION_PROVIDER_CHANGED );
sendBroadcast(intent);

Easy? yes. But I hate it.
Intent.ACTION_PROVIDER_CHANGED is a constant String "android.intent.action.PROVIDER_CHANGED". Intents can be any String + more data. And you'll only know if what you wrote should work is at runtime. I hate it because these things aren't checked at compile time where in my opinion it should be. If you're calling standard Intents its fairly, but other people's Intents or your own Intents make life harder because it's not always discernible in code what the strings should be and what the extra data should be.

I'm not sold on it, and it's all over the API. I have the same problem with URIs, but that will be for yet another post.

Thursday, December 11, 2008

10,000 downloads

This morning ToggleWifi passed 10,000 downloads. That number to me is a little staggering considering that until now the code that I've written has only been used by the companies I've work for.

Certainly that alone gives me motivation to keep producing anything that comes to mind. The comments and emails I have received have also been overwhelming. But what really gets me is seeing my apps mentioned elsewhere.

I couldn't help doing a google search for "togglewifi android".

Here is someone who mentions my apps (ToggleWifi and ToggleBlu) as two of his most favorite.
Here is a thread on AndroidCommunity.com discussing ToggleWifi.
Here my app is mentioned in... whatever language that is.
Here which has a small mention of me by full name.

Wednesday, December 10, 2008

Reflection Continued

In addiction to being somewhat messy, reflection can be used in dangerous ways. Remember you are using code in which the programmer never intended. Sometimes this can result in humorous things (see Crashing an in flight entertainment system). Sometimes your great plans can go down in flames.

Looking through the source code of the Android platform (which can be downloaded very easily from here if you have a mac or linux computer), you can find out how to do things that are not in the standard source. In this case I was looking for how to enable/disable GPS for my ToggleGPS app.

You can see the code for the Security and Location settings screen here.

It's not for the faint of heart. I wouldn't suggest doing this except for experienced developers because a lot of times you encounter code and terminology that you haven't seen before.

All the magic of enabling GPS happens with this line

// Inform the location manager about the changes
mLocationManager.updateProviders();

However, if you check out the Android SDK reference for LocationManager you find out that there isn't an updateProviders() method.

It's private, it's hidden. How google is calling it here I'm not really sure. We can get the LocationManager in our code by

LocationManager locationManager =
(LocationManager) getSystemService(Context.LOCATION_SERVICE);

And as I showed in my last post you can access "hidden" methods using reflection.

Method updateProvidersMethod =
locationManager.getClass().getMethod("updateProviders");
updateProvidersMethod.setAccessible(true);
updateProvidersMethod.invoke(locationManager);

But...

Under the hood things are not as they seem. If you try to run this in code in the emulator you get a very strange exception. Buried in the stack trace you find:

...
W/System.err( 415): Caused by: java.lang.SecurityException: Permission Denial:
getIntentSender() from pid=415, uid=10023 is not allowed to send as package andr
oid
...

Now I didn't bother looking through the Android code to figure out what is going on under the hood, but the message "not allowed to send as package android" stopped me in my tracks. You can use reflection to trick java into doing what you want it to do but this you can't trick. I'm guessing, but the error message probably means that I was trying to access operating system protected code.

But there ARE apps in the Android Market that can do this. But how?

The answer to this is surprisingly simple. But that is for another post.

Tuesday, December 9, 2008

Hacking the "Private" Android API

So Android doesn't allow you to interact with Bluetooth. The functionality exists, however developers can't access it... or can they?

There are many apps on the Android Market that toggle Bluetooth. So if Google doesn't give developers the ability how do they do it? The answer is an advanced Java concept called Reflection (http://java.sun.com/docs/books/tutorial/reflect/).

The idea is that even if you don't know what object you are using, you can find out information about it and then use that information to call methods that you otherwise shouldn't know about it.

The way this works is that Google is doing something strange.
In order access most services on Android you call the Context.getSystemService() method passing in a string signifying the service you want to access. You won't find this information in the API but if you pass the string, "bluetooth" you are returned an object whose class isn't in the standard API. This is the Bluetooth Manager. However the class is generic, it's of type Object.

But now you can use Reflection to find it's methods.

Object manager = getSystemService("bluetooth");
Class c = manager.getClass();
Method enable = c.getMethod("enable");
enable.setAccessible(true);
enable.invoke(manager);

... and then you've enabled Bluetooth.

Instead of "enable" you can put the name of any other method of the Bluetooth Manager class. You can get a list of all the methods by calling getMethods().

It's not an efficient way to program, it definitely doesn't produce nice and clear code. But it's a very valuable tool to know.