Tuesday, April 19, 2011

How to Patch A Stock Android Application

Introduction

When developing an Android application that depended on the stock Android application Sound Recorder, I found a bug in the latter application. This post describes how one would patch such a stock Android application and submit the fix. The bug and the fix are not as relevant as the process of working with the Android Open Source Project (AOSP) code base. Gaps in the source documentation are filled in here.

Note that this post is tailored to development in Ubuntu 10.10 as that was my environment.

Prerequisites

Before fetching and building the AOSP, one should realize or do a few things:
  • Search the Android issue tracker and mailing lists to figure out if anyone has encountered this before and if a fix is in progress.
  • Browse the source to try to diagnose the issue before doing anything. Hooray for open source! Each application is in a separate git tree.
  • Once the issue is diagnosed and if it's not already an existing issue, post to the Android issue tracker. This allows others to see the status of the bug, make comments, and get others involved in a fix. If you can't fix it, at least someone else will be aware of the issue and could take on the effort.
  • It seems like the entire AOSP has to be built in order to build the single stock application that one wishes to patch. I could not find a source that described more surgical building without building everything else. Stock applications use private APIs that are not published in the SDK so those dependencies have to be built. It's probably just easiest to build everything to get to the task at hand.

Initializing the Build Environment

The official steps at the time of the writing work with the following exceptions:
  • OpenJDK 6 works. You do not have to get the Sun JDK. To check your installation, use the command "java -version" in a terminal.
  • The required packages list is wrong; apt-get will complain that it cannot locate packages. See the first answer on this Stack Exchange post.

Downloading the Source

Nothing wrong actually happened here using the official steps! Download time varies depending on your interwebs connection; it took several minutes for me. My working directory was $HOME/aosp; all following instructions will assume this is your current directory.

Building and Running

The official steps did not indicate how much pain would be involved. A bunch of issues were encountered.

Processor Architecture 32-bit vs. 64-bit

Based on mailing list discussions, building is only supported for 64-bit machines for releases after Froyo (v2.2), but the community has found ways to patch the source to allow 32-bit builds. When trying to build on a 32-bit machine, you'll get a warning from the make command. A few makefiles need to be editing for building to proceed. Do as follows:
  • In build/core/main.mk, comment out the check for 64-bit versions (search for 64 to find the if-statement).
  • Search for makefiles with explicit 64-bit build flags and comment out variable assignments that add those flags:
    find . -name '*.mk' | xargs grep m64
For example, here is the changes I made to one of the makefiles with explicit flags:
diff --git a/wpa_supplicant/Android.mk b/wpa_supplicant/Android.mk
index d3ab3dd..be99ad4 100644
--- a/wpa_supplicant/Android.mk
+++ b/wpa_supplicant/Android.mk
@@ -249,9 +249,9 @@ LDFLAGS += -F/System/Library/PrivateFrameworks -framework Apple80211
 endif
 
 ifdef CONFIG_DRIVER_PS3
-L_CFLAGS += -DCONFIG_DRIVER_PS3 -m64
+L_CFLAGS += -DCONFIG_DRIVER_PS3 # -m64
 OBJS_d += src/drivers/driver_ps3.c
-LDFLAGS += -m64
+#LDFLAGS += -m64
 endif
 
 ifdef CONFIG_DRIVER_IPHONE

Once the build starts, it took at least an hour on my machine (Core 2 Duo, 2.0 Ghz). It's building almost everything you can think of except the kernel. The built binaries will be in the out directory.

Emulator Fails to Run

Before applying our fix, let's at least make sure that our environment works. Run the emulator at out/host/linux-<ARCH>/bin/emulator.
emulator: ERROR: You did not specify a virtual device name, and the system directory could not be found.

If you are an Android SDK user, please use '@' or '-avd ' to start a given virtual device (see -help-avd for details).

Otherwise, follow the instructions in -help-disk-images to start the emulator

By default, the emulator looks for default system images (*.img), containing the root file system among other things, in $ANDROID_PRODUCT_OUT. This shell variable is defined only if you ran the lunch program in the same shell as the emulator. You can rerun lunch in any shell to define these variables.

Do not use a prebuilt SDK to test applications built from the master branch of AOSP. They are built with a special SDK version that is newer than any prebuilt SDK version; therefore, the patched application will fail to install (INSTALL_FAILED_OLDER_SDK). Using the Android Asset Packaging Tool (aapt), we can see this special version of the applications built:

$ aapt dump badging $ANDROID_PRODUCT_OUT/system/app/SoundRecorder.apk 
package: name='com.android.soundrecorder' versionCode='10' versionName='AOSP'
sdkVersion:'AOSP'
targetSdkVersion:'AOSP'

No SD Card

If testing requires a SD card, create one that the emulator can use as follows:
out/host/linux-<ARCH>/bin/mksdcard SIZE $ANDROID_PRODUCT_OUT/sdcard.img
See the help documentation for how to express size (mksdcard -help). For example, my SIZE was 32M to produce a 32 MiB file. The second argument is the default location of the emulator's SD card image file.

Using a Test Application

I used my Android application to exercise to demonstrate the bug in the stock application. To install your application, use the Android Debug Bridge (adb):
$ out/host/linux-<ARCH>/bin/adb install LOCATION/OF/TEST/APP

Applying Your Fix

Assuming that the emulator is operational and you've demonstrated the bug, apply the fix and rebuild. The shell function mm is defined in build/env.sh and will rebuild only the module in the current directory.
$ cd packages/apps/THE_APP_TO_FIX
$ (edit files as necessary)
$ mm 

Restart the emulator to pick up the changes. Even though the Android Debug Bridge (adb) provides a sync capability to push the changed file to the emulator, you might get an "out of memory" error. Let's keep it simple for now.


Submitting the Fix

After the fix has been verified and committed locally, it's time to submit it to the world! Before you can use the repo tool to upload the fix, set up your account on r.android.com:

  • Submit your SSH public key as uploading uses the SSH protocol.
  • Sign a contributor agreement. Individuals can do this completely online.

The successful upload will be viewable at the source review site. To improve the chance of getting accepted, add a reviewer who was involved in the development of the application. The commit log should indicate who would be most appropriate as a reviewer.

Closing

For such a simple patch, I ended up doing a lot of work, but it was worth it in the end. I've been a user of open source for a while but have never contributed back until recently. Even though it is a small contribution, it's a start. A lot of people making small contributions add to something big. Also, it's awesome to think that my small change could be distributed to millions of devices.

No comments:

Post a Comment