Android Programming: Pushing the Limits (2014)
Part III. Pushing the Limits
Chapter 16. Hacking the Android Platform
Although I expect most of you will be developing Android applications, sometimes using the NDK (refer to
Chapter 14) and sometimes using hidden APIs (refer to Chapter 15), some of you may have specific reasons
to modify the Android platform. Google provides the source code for the Android platform as an open-
source project called (you guessed it) Android Open Source Project (AOSP) just so third-party developers can
experiment and modify Android to suit their needs.
You can download all the code and compile it, but you also need a supported device if you want to test
your modifications on real hardware. Fortunately, all the devices announced under the Google Nexus brand
allow you to do so, and many manufacturers, such as Sony Mobile and HTC, allow you to unlock their devices
and flash them with a custom firmware as well. A company working on a low-level software feature that it
intends to license to handset manufacturers can use this approach to demonstrate its products on real devices,
for example.
Until Google released Android as an open-source project and provided open devices, this approach required
expensive development platforms from chipset vendors like Qualcomm and Texas Instruments. Today, hundreds
of independent developers spend a significant amount of time hacking on their own version of the Android
platform. Some of these developers have organized themselves into community groups focusing on building
custom firmware for Android devices that users can download and install.
In this chapter, I describe how you can build your own firmware or a custom ROM for an Android device,
including how to access and download the AOSP source code, set up the build environment, compile the
code, and flash the firmware to a device. In this chapter, I’m using the Galaxy Nexus, but you can use any of the
Google Nexus-branded devices with the same instructions. I also provide some general directions on how to
proceed with building custom firmware for other manufacturers’ devices.
In addition, I describe how to modify and extend the Android platform. Because the Android source code is
very large, I selected a few simple examples that will introduce you to the platform’s design. If your company is
developing custom solutions for the Android platform, you should find this information valuable.
I end this chapter talking about how you can contribute your changes to the Android platform to the AOSP.
If you think a feature is missing and not prioritized by Google, you can use this approach to make it happen.
( Note: This isn’t a guarantee Google will accept your patch, but you’ll never know unless you try.)
A word of caution: The things described in this chapter can, if you’re not careful, break your device. If
you need to do this, use a device that is not your primary phone and know that your actions could
render the device unusable or break the warranty. When you flash your device, all its content will be
deleted, so be sure to back up content you want to save.
Several of the major Android device manufacturers allow you to flash a custom firmware on their devices, which
you do by unlocking the bootloader so that the device will accept firmware images with signatures other than
the device’s manufacturer. Doing so usually affects the warranty of the device, but if your goal is to demonstrate
a new low-level feature to that manufacturer, it might be worth doing. I briefly cover some of the major
manufacturers approach to unlocking the bootloader later in this chapter.
Unlocking Your Device
All Android devices come with a locked bootloader that you need to unlock before you can flash them with
new firmware (this also applies to flashing the factory firmwares from the link in the section “Flashing Factory
Images”). You can unlock all Google Nexus devices with a simple command. The following instructions apply to
Google Nexus devices.
Start by connecting your device to your computer and ensure that USB Debugging is enabled (see Chapter 1).
Next, reboot the device to the bootloader using adb as shown here:
$ adb reboot-bootloader
Your device will reboot into the bootloader, as shown in Figure 16-1, where you can issue fastboot
commands, which are used to unlock the device and flash new firmware images.
Figure 16-1 A Galaxy Nexus device in fastboot mode with the bootloader locked
Next, perform the following command, which unlocks the bootloader so you can flash your custom firmware
images:
$ fastboot oem unlock
It’s also possible to lock the bootloader again by issuing the command fastboot oem lock.
The screen of the device changes (see Figure 16-2) and shows information about the effects of unlocking
the bootloader. Press Volume Up/Down to select your choice and press the Power button to confirm. When
unlocked, the device returns to the fastboot mode and allows you to flash a new firmware image.
Figure 16-2 The warning box presented when you try to unlock the bootloader on a
Galaxy Nexus device
Flashing Factory Images
When you build your custom firmware and test your new features, the device is likely to crash. Although
generally you can return to fastboot mode and flash a new firmware image, sometimes it may be useful to
flash the factory image to the device. You may also want to do so when testing an older version of Android.
Download the factory image for your device from https://developers.google.com/android/
nexus/images and extract the archive into an empty directory.
In the directory, you’ll find a shell script named flash-all.sh, which will flash the entire factory firmware
to the device and restore it to its original state. After the process is complete (it could take a few minutes,
depending on the device and your computer), the device reboots into the freshly installed Android image.
Unlocking Non-Google Nexus Devices
Most Android devices on the market aren’t Google Nexus brands, and not all of these device’s manufacturers
allow you to unlock the bootloader and flash a custom firmware. However, more and more manufacturers are
realizing the positive benefits of allowing advanced users and third-party companies to unlock and flash their
devices with custom firmware, so the list of officially unlockable devices is growing all the time.
Sony Mobile was the first manufacturer to announce that its devices could be unlocked, beginning with its
2011 device. Not every device after 2011 from Sony Mobile can be unlocked, and sometimes the devices sold
through operators can’t be unlocked. You can find more information about unlocking the Sony Mobile Android
devices at http://unlockbootloader.sonymobile.com.
HTC also provides an official method for unlocking its devices. Some devices require a firmware update before
they can be unlocked. More information is available at http://www.htcdev.com/bootloader.
Motorola has an official bootloader unlock program covering some of its devices. To find out if this program
covers your device, visit https://motorola-global-portal.custhelp.com/app/standalone/
bootloader/unlock-your-device-a.
Other manufacturers may also allow you to unlock your device. Visit their support sites for more information.
Also, even if there is no official way of unlocking your device, there may be an unofficial workaround. A good
place to start is on the XDA Developers forum (http://www.xda-developers.com). Remember that
unlocking your device this way will usually void the warranty. Also, even if you manage to unlock the device,
most manufacturers don’t provide a full set of hardware drivers necessary to build a working custom firmware.
Refer to the each manufacturer’s information on the details.
Community-Supported Firmwares
Ever since the first Android devices were released, a stable and growing community of developers has worked
on releasing custom firmware that users can download and flash onto their own devices. The most well-known
group is CyanogenMod, and it has custom firmware for a wide selection of devices. You can find more
information about its work at http://www.cyanogenmod.org.
Other groups have focused on devices from specific vendors. For instance, a group called the FreeXperia Project
is building high-quality community firmware for the Sony Mobile Xperia devices. Visit its site at http://free
xperiaproject.com for more information.
You can usually find the best source for community-developed custom firmware at the XDA Developer forums
(see http://forum.xda-developers.com). At this site, you can find information about most devices
that have been released.
The Android Source Code
The Android Open Source Project is the publically available source code for the Android platform, and Google
provides it at http://source.android.com. This site includes not only the source code for the Android
platform, but also for the SDK and other development tools. The project is structured around a number of Git
modules. Each module is part of the overall system, such as the Linux kernel, native libraries, the Dalvik VM, or a
system application.
Setting Up the Build Environment
You can work with the Android Open Source Project on Linux and Mac OS X. The build instructions tend to
be updated on a regular basis, with support for new versions of the host operating systems and updated
development tools, so I’m not covering the instructions on how to set up the environment on your system. You
can find the latest instructions for setting up the build environment for AOSP at http://source.android.
com/source/initializing.html.
Because the default partition on OS X is case-insensitive, for Mac OS X, you need to create a new case-sensitive
disk image. Also, downloading the source code (using repo sync) the first time will take quite a while—a lot
of code must be downloaded.
Nexus Binaries
Although the Android platform is open-source, all the hardware drivers for the Nexus devices are not. For
instance, the graphics driver for Galaxy Nexus is a proprietary library from Imagination Technologies, and the
source code for this hasn’t been released yet. Therefore, you will also need to download all the binary drivers for
your Nexus device before you proceed with building a custom firmware. You can find the available binaries at
https://developers.google.com/android/nexus/drivers.
You download all the binary drivers available for your device to the root of the Android platform projects you’ve
just downloaded. Run the following command to extract the archive, and you’ll be prompted with a license
agreement:
$ tar xzvf imgtec-maguro-jdq39-bb3c4e4e.tgz
x extract-imgtec-maguro.sh
$ sh ./extract-imgtec-maguro.sh
The license for this software will now be displayed.
You must agree to this license before using this software.
-n Press Enter to view the license
After you agree to the terms of the license, you have new projects under the vendor directory. These are
usually only headers, makefiles, and binary .so files that will be installed on the custom firmware when you
finish building.
It’s important to include all the binary drivers available, or the custom firmware you’ve built probably won’t
boot correctly.
Building and Flashing
After all the source code is downloaded and the development environment is set up, you can start building the
custom firmware. Make sure ccache is installed and configured (see http://source.android.com/
source/initializing.html#setting-up-ccache) because it will speed up subsequent builds.
Building the platform the first time will take a bit of time (about 11⁄2 hours on my MacBook Pro with 16GB RAM).
First, you need to load the build environment into your current shell and tell the build system which device and
configuration you want to build.
$ source build/envsetup.sh
$ lunch full_maguro-userdebug
The first command loads the build system and sets all the right environment variables. The lunch command lets
you select the build target (when it’s called without arguments, you can manually select the target). In this case,
you want to build the userdebug configuration for the Galaxy Nexus (“maguro”). The userdebug configuration
allows you to access adb with root access and provides more privileges that enable you to debug the device and
write to the system partition, which is useful if you want to install a new system application later on.
Because building can take a significant amount of time, I suggest building on a high-end computer with a fast
disk (preferably a SSD)—although it’s possible to compile the platform on a low-end computer. To speed things
up, instruct the build system to use additional threads while building by giving the argument –jN to the make
command, where N is twice the number of hardware threads your computer supports. Generally, computers
can support two hardware threads per CPU core (using hyper-threading), so a computer with one CPU with four
cores will support eight threads.
$ make –j8
This is a good time to go and make some more coffee.
When the build is complete and, hopefully, successful, you can reboot your connected device in fastboot mode
and flash the firmware.
$ adb reboot-bootloader
$ fastboot flashall –w
When the process is complete, your device should boot up with your new custom firmware. Try running the
following commands from a terminal to confirm that you have root access:
$ adb root
restarting adbd as root
$ adb remount
remount succeeded
$ adb shell
root@android:/ #
The first command restarts the adb daemon on the device to run as root, giving it additional privileges. The
second command remounts the system partition on the device in read/write mode, allowing you to install
new system applications under /system/app. The final command simply confirms that you have root access
by opening a shell on the device. If the command line starts with root@android, you know that you have
root access.
Congratulations, you now have your very own custom firmware on which you have full root control and the
possibility to install new system and platform-signed applications.
Writing System Applications
As I describe in Chapter 3 and 12, Android’s permission system has various protectionLevels that describe
who can use a specific permission. If the permission has its protectionLevel set to system, the application
that uses this permission must initially be installed on the system partition (/system/app, to be specific). In
this way, third-party developers can build applications that the manufacturer installs on the system partition
and which then have access to system-level permissions. Note that updates to this application will retain the
same permissions, even though the updates are placed on the data partition. This is how Google’s Android
applications (such as Gmail, Google+, and Google Map) can gain additional permissions even though the device
manufacturer doesn’t sign them.
As a third-party developer, as long as you can get your application in the system partition for a device, you can
provide upgrades to users through the Google Play Store.
Along with the challenge of getting your system application on a manufacturer’s device, you should
also define a new system permission that the manufacturer then adds to the system permissions. By
using this permission in your system application, you can effectively filter the application on Google
Play to make it invisible to users who don’t have these modifications.
To install an APK as a system application, you need to have write access to the system partition (see the end
of the “Building and Flashing” section). Then you can push the new APK to the system application folder as
shown here:
$ adb push <apk file> /system/app
After the application is placed on the system partition, the package manager sets it up properly. Your
application can now use permissions requiring protectionLevel system, even though it isn’t signed with
the manufacturer’s platform certificate.
The signature permissionLevel is usually set for even more-sensitive permissions. For instance,
the permission android.permission.FORCE_STOP_PACKAGES has its protectionLevel set
to signature, meaning that only applications signed with the vendor platform certificate can use APIs
that require this. Third-party developers who want to sell their solution to a manufacturer, which requires
signature permissions can do so by providing a project for the platform build with an unsigned APK, similar
to how hardware drivers are distributed for the Nexus devices.
Platform Certificates
You can find the default test keys used to sign your custom firmware under <aosp root>/build/target/
product/security. Because these keys are available publically, never consider them safe. Refer to the
README file in this directory for additional information and instructions on how to generate your own platform
keys. However, you can use them for developmental purposes when you want to test new system applications.
The format of these keys is different from the one used to sign Android applications using the Android SDK or
Android Studio, so before you can use them in your development environment, you need to convert them.
$ openssl pkcs8 -inform DER -nocrypt -in platform.pk8 -out platform.pem
$ openssl pkcs12 -export -in platform.x509.pem -inkey platform.pem -out
platform.p12 -name android-platform -password pass: <password>
$ keytool -importkeystore -destkeystore platform.keystore -srckeystore
platform.p12 -srcstoretype PKCS12
You now have a keystore with the format recognized by the Android development tools. Simply use this
keystore when you want to sign your application with the platform certificate. Doing so allows your application
to use all permissions requiring signature permissionLevel. Also, you can now set the sharedUserId
attribute in the manifest to android.uid.system, which gives the application the same user ID as the
system server and grants access to additional system-level APIs.
To use this certificate in your application, copy the keystore to your application and add the following to your
Gradle build:
android {
signingConfigs {
debug {
storeFile file(“platform.keystore”)
storePassword “password”
keyAlias “android-platform”
keyPassword “password”
}
}
// ... omitted for brevity ..
}
Now subsequent debug builds from Android Studio must use the extracted platform key when signing your
application.
Remember: Never use this certificate for applications published on the Google Play Store. Only use it
for testing and demonstration.
Writing Signature-Signed Applications
In Chapter 15, I discuss how to use the hidden platform APIs in the WifiManager to detect whether tethering
is enabled. Using some of the additional hidden APIs together with the platform key, you can build your own
application for controlling Wi-Fi tethering, as shown in the following example:
<?xml version=”1.0” encoding=”utf-8”?>
<manifest xmlns:android=”http://schemas.android.com/apk/res/android”
package=”com.aptl.systemlevelapps”
android:sharedUserId=”android.uid.system”
android:versionCode=”1”
android:versionName=”1.0” >
<uses-permission android:name=”android.permission.CHANGE_WIFI_STATE”
/>
<uses-sdk
android:minSdkVersion=”17”
android:targetSdkVersion=”17” />
<application
android:allowBackup=”true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme” >
<activity
android:name=”.EnableTetheringActivity”
android:label=”@string/app_name” >
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>
</application>
</manifest>
First, you add sharedUserId with the value android.uid.system to the manifest element to give your
application the correct user ID for controlling the Wi-Fi. Even though your application is now signed with the
platform certificate, you still need to declare the permissions your application requires, which is why you need
to add the permission CHANGE_WIFI_STATE to the manifest.
public class EnableTetheringActivity extends Activity {
private static final String TAG = “EnableTetheringActivity”;
private WifiManager mWifiManager;
private Method mSetWifiApEnabledMethod;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mWifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
try {
Class clazz = WifiManager.class;
mSetWifiApEnabledMethod = clazz.getMethod(“setWifiApEnabled”,
WifiConfiguration.class, boolean.class);
} catch (NoSuchMethodException e) {
Log.e(TAG, “Error retrieving method setWifiApEnabled()”, e);
}
setContentView(R.layout.activity_main);
}
public void doEnableWifiTethering(View view) {
try {
if(mWifiManager.isWifiEnabled()) {
mWifiManager.setWifiEnabled(false);
}
EditText ssidNameView = (EditText) findViewById(R.id.ssid_name);
String ssidName = ssidNameView.getText().toString();
EditText wifiPasswordView =
(EditText) findViewById(R.id.wifi_password);
String wifiPassword = wifiPasswordView.getText().toString();
WifiConfiguration wifiConfiguration = new WifiConfiguration();
wifiConfiguration.SSID = ssidName;
wifiConfiguration.preSharedKey = wifiPassword;
mSetWifiApEnabledMethod.invoke(mWifiManager,
wifiConfiguration, true);
} catch (IllegalAccessException e) {
Log.e(TAG, “Illegal access.”, e);
} catch (InvocationTargetException e) {
Log.e(TAG, “Invocation error.”, e);
}
}
}
The Activity shown here retrieves the hidden method setWifiApEnabled() from the WifiManager
class. When the click-callback doEnableWifiTethering() is called, the SSID and password are retrieved
and used to construct a default WifiConfiguration, which is then used as a parameter when invoking the
Method object.
You now have a way of controlling the state of Wi-Fi tethering through a standard Android application, which
just happens to be signed with the platform certificate.
Hacking the Android Platform
In this section, I describe how to work with the AOSP source code. The code examples are in Java, but the same
approach applies to native development as well.
Setting Up Your IDE
Although Android Studio works fine for developing regular Android applications, I recommend using
IntelliJ IDEA CE (Community Edition) for working with the AOSP source code. You can find this IDE at www.
jetbrains.com/idea. It’s also possible to work with AOSP in Eclipse, but I recommend IntelliJ IDEA CE
because of its superior performance and code navigation support.
Before opening the IDE and importing the source code, make a complete build as described in the section
“Building and Flashing” earlier in this chapter. Doing so generates the Java source files for platform resources,
which makes it easier to work with from the IDE.
After you have a complete build, you can generate the IntelliJ project files (and Eclipse .classpath file) by
executing the following command in the project root directory:
$ development/tools/idegen/idegen.sh
Read excludes: 3ms
Traversed tree: 50027ms
Now, you have two IntelliJ IDEA files in the root named android.ipr and android.iml.
Start IntelliJ IDEA CE and open the project file (android.ipr)—the first time you do this, IntelliJ will build an
index of all the source code, so it will take some time.
You can now start hacking the AOSP source code.
Android Projects
The Android platform consists of a number of projects, each in its own Git repository. Because there are more
than 300 of them, there’s not enough room in this book to cover and explain every project in the AOSP source
code. Instead, I’ll describe some of the more important ones, based on their path in the file system. Some
consist of several Git repositories (for instance, the packages project).
You can find a complete list of the projects in the file <aosp root>/.repo/project.list.
Frameworks/base
The central (and according to some, most important) project in the Android platform is frameworks/base.
This project contains most of the sources for the system server as well as many other components running
on an Android device. Here, you’ll also find the implementation of most of the Android APIs (except those
belonging to the java and javax packages). If you want to add a new system service or modify the Android
APIs, you make your changes here.
Packages
You’ll find all of the standard system applications in the packages project, divided into apps,
inputmethods, providers, screensavers, and wallpapers folders. The apps folder contains the
applications the user can see in the launcher, such as Email, Phone, and Calculator. The Providers folder
contains the system ContentProviders, such as the media or contacts provider. The Inputmethods
folder contains the default soft keyboards found in Android.
If you want to modify one of the existing system applications, this is where you start. Developers often start
modifying the default launcher application in the AOSP sources. You can find this app under packages/
apps/Launcher2.
Vendor
The vendor directory is not a single project but the place where vendor-specific platform projects
are contained. This is usually where the binary hardware drivers are placed. If you intend to provide a
manufacturer with your own project for the Android platform, such as a shared library with some proprietary
implementations, place it in this directory as well. Also, it’s best to place vendor-specific applications that
should be preinstalled in this directory. In fact, most vendor-specific code should end up here; this way, device
manufacturers can keep the Android Open Source Project code clean from source code that isn’t open-source.
Here is the Android.mk for the Wi-Fi and Bluetooth drivers from Broadcom for the Galaxy Nexus:
LOCAL_PATH := $(call my-dir)
ifeq ($(TARGET_DEVICE),maguro)
include $(CLEAR_VARS)
LOCAL_MODULE := bcm4330
LOCAL_MODULE_OWNER := broadcom
LOCAL_SRC_FILES := bcm4330.hcd
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_SUFFIX := .hcd
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR)/firmware
include $(BUILD_PREBUILT)
endif
You can find this makefile in <aosp root>/vendor/broadcom/maguro/prebuilt. The content of this
file is very similar to what a normal Android.mk looks like when you’re doing native development using the
Android NDK. The source is the binary file bcm4330.hcd, and its output is in the firmware directory on
the device.
When building your own vendor extension, refer to the existing build files for other projects and construct your
own. For Android applications (APK) that you want to include in the system partition, refer to the build files
found in the packages directory for examples of how your Android.mk should look.
Android Linux Kernel
When you’re building Android for a Nexus device, note that the Linux kernel is usually prebuilt and can be found
under <aosp root>/device (for instance, <aosp root>/device/samsung/tuna for the Galaxy
Nexus). If you need to modify the kernel for your device, you must download the Linux source code for that
device, modify the build configuration, build the image, and copy it to the right location.
The Linux kernel has a different license (GPLv2) than the Android platform (usually Apache License v2). The
interesting thing with the Linux kernel is that this license guarantees that you can get access to the source code
for every single Android device. While modifying the Linux kernel for your own custom firmware is beyond
the scope of this book, you can refer to the instructions from the chipset vendor for each device to learn more
about kernel development on its hardware platform.
Adding a System Service
When modifying the Android platform, a common task is to add a new system service. These are the services
that Android developers get when calling Context.getSystemService(). In the following code, I
show you how to add a new system service by modifying the code in frameworks/base. I call this service
HomeDetector, and its task is to send a broadcast when the device enters the location defined as home. This
class belongs to the location APIs; thus it is placed in the android.location package.
public class HomeDetector {
private static final String TAG = “HomeDetector”;
/**
* Sticky broadcast indicating that the device arrives
* or leave the defined home wifi.
*
*/
public static final String ACTION_HOME_LOCATION_CHANGED =
“android.location.HOME_LOCATION_CHANGED”;
/**
* Extra <code>boolean</code> indicating if the device arrived
* to the home location or not.
*/
public static final String EXTRA_AT_HOME = “atHome”;
private IHomeDetector mHomeDetectorService;
public HomeDetector(IHomeDetector homeDetector) {
mHomeDetectorService = homeDetector;
}
/**
* Set the home wifi.
*
* @param homeWifi
* @hide Should only be available to system applications
*/
public void setHomeWifi(WifiInfo wifiInfo) {
try {
mHomeDetectorService.setHomeWifi(wifiInfo);
} catch(RemoteException e) {
Log.e(TAG, “setHomeLocation.”, e);
}
}
}
The class HomeDetector will be the class that is returned when an application calls Context.
getSystemService() with the parameter Contex.HOME_DETECTOR (which you also need to add to the
Context class). This class takes an instance of HomeDetectorService as a constructor parameter, which
will contain the actual implementation for the service.
When adding a new system service in Android, the standard approach is to define an AIDL file that will be
used internally by the system server. You create this AIDL in the same package as the HomeDetector class
(android.location).
package android.location;
import android.location.Location;
oneway interface IHomeDetector {
void setHomeWifi(in WifiInfo homeWifi);
}
The AIDL is compiled as an ordinary application-specific AIDL and allows you to implement the other parts of
your new system service.
The following code goes into SystemServer.run() in the package com.android.server. This is the
central class for all system services.
try {
Slog.i(TAG, “Home Detector”);
homeDetector = new HomeDetectorService(context);
ServiceManager.addService(Context.HOME_DETECTOR, homeDetector);
} catch (Throwable e) {
reportWtf(“starting Home Detector”, e);
}
The preceding code simply creates an instance of HomeDetectorService and starts the service.
Here is the actual implementation of the new service:
public class HomeDetectorService extends IHomeDetector.Stub {
private static final String TAG = “HomeDetectorService”;
public static final String HOME_WIFI_SSID = “homeDetector.ssid”;
public static final String HOME_WIFI_BSSID = “homeDetector.bssid”;
public static final String SET_HOME_WIFI_PERMISSION
= “android.permission.SET_HOME_WIFI”;
private Context mContext;
private boolean mCurrentState = false;
public HomeDetectorService(Context context, WifiManager wifiManager) {
mContext = context;
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
if(wifiInfo != null) {
String ssid = SystemProperties.get(HOME_WIFI_SSID);
String bssid = SystemProperties.get(HOME_WIFI_BSSID);
mCurrentState = wifiInfo.getSSID().equals(ssid)
&& wifiInfo.getBSSID().
equals(bssid);
}
sendHomeDetectionBroadcast(mCurrentState);
IntentFilter intentFilter =
new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_
ACTION);
mContext.registerReceiver(new WifiListener(), intentFilter);
}
public void setHomeWifi(WifiInfo homeWifi) throws RemoteException {
mContext.enforceCallingPermission(SET_HOME_WIFI_PERMISSION,
“Missing permission “ + SET_HOME_WIFI_PERMISSION);
SystemProperties.set(HOME_WIFI_SSID, homeWifi.getSSID());
SystemProperties.set(HOME_WIFI_BSSID, homeWifi.getBSSID());
}
public void sendHomeDetectionBroadcast(boolean state) {
Intent homeDetectorBroadcast =
new Intent(HomeDetector.ACTION_HOME_LOCATION_
CHANGED);
homeDetectorBroadcast.putExtra(HomeDetector.EXTRA_AT_HOME, state);
mContext.sendStickyBroadcast(homeDetectorBroadcast);
}
class WifiListener extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
NetworkInfo networkInfo = intent.
getParcelableExtra(WifiManager.EXTRA_NETWORK_
INFO);
boolean newState = false;
if(networkInfo.getState().equals(NetworkInfo.State.CONNECTED))
{
String ssid = SystemProperties.get(HOME_WIFI_SSID);
String bssid = SystemProperties.get(HOME_WIFI_BSSID);
WifiInfo wifiInfo =
intent.getParcelableExtra(WifiManager.EXTRA_WIFI_
INFO);
newState = wifiInfo.getSSID().equals(ssid) &&
wifiInfo.getBSSID().
equals(bssid);
}
// Only send new broadcast if changed
if(newState != mCurrentState) {
mCurrentState = newState;
sendHomeDetectionBroadcast(mCurrentState);
}
}
}
}
The usual place to put a new system service is under com.android.server.<category>, where
<category> is the type of service you created (in this case, location). Note the call to Context.
enforceCallingPermission(), which verifies that the calling process has the right to call this method.
In this case, I created a passive service, but if your service needs to run its own thread, you need to implement
the Runnable interface and initialize a new Looper in the run() method, as shown here:
@Override
public void run() {
// Set the correct thread priority
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Looper.prepare(); // Prepare the Looper
// Create a custom Handler for processing
mServiceHandler = new MySystemServiceHandler();
init(); // Perform initialization
Looper.loop(); // Start this thread as a Looper
}
With the run() method just shown, your background service can post Messages to the custom Handler
that has its own dedicated thread. In this way, you can perform blocking operations in your service that
otherwise could block the entire system.
The final step is to declare the new permission required for the setHomeWifi() method, which you do by
editing the AndroidManifest.xml of the system server, located in <aosp root>/frameworks/base/
core/res/. Locate the permissions belonging to the location group (android.permission-group.
LOCATION) and add the new permission there.
<!-- Allow an application to set the WiFi which should
be considered “home” -->
<permission android:name=”android.permission.SET_HOME_WIFI”
android:permissionGroup=”android.permission-group.LOCATION”
android:protectionLevel=”signature|system”
android:label=”@string/permlab_setHomeWifi”
android:description=”@string/permdesc_setHomeWifi” />
In this case, only system or signature applications are allowed to use this permission. If you want to create
a new public API that anyone can access, consider whether the protectionLevel should be normal or
dangerous.
Speeding Up the Platform Development Cycle
After you add your new system service, you’re ready to build the code and push the changes onto your device.
Because building the entire platform from scratch every time would take a significant amount of time, you can
use some tricks to reduce the turnaround time for this kind of development.
For example, instead of building the entire platform, build only the project you’ve changed. In the earlier
example in “Adding a System Service,” you modified the project frameworks/base, so give this project as a
parameter to the make command so that only that part is rebuilt.
$ make –j8 frameworks/base
This command compiles the code and builds the binaries for that specific project. Then you can push only the
changed binaries onto the Android device with the following commands:
$ adb shell stop
$ adb sync
$ adb shell start
The first command stops the Android system server, allowing you to safely push the new binaries onto your
devices without crashing them. The second command pushes the actual binaries. This command can also take
a second parameter stating which directory you want to synchronize. Finally, you call the third command to
restart the system server. Your newly modified system server will now start up together with the new service
you just added. This procedure lets you iterate through the development cycle quickly and try out small
changes to the system service and libraries, without the time-consuming task of having to shut down the
phone and flash a new firmware.
The preceding procedure doesn’t produce a new firmware image for each build. To do this, you have
to perform a new, full build. However, because you (hopefully) have enabled ccache and haven’t
deleted your build output directory, the second time around shouldn’t take too long.
Contributing to AOSP
Google is doing most of the new development on the Android platform, but a significant amount of code is
contributed by other companies and individuals through the Android Open Source Project. Manufacturers of
Android devices and chipset vendors are the largest contributors, but smaller companies also fix many bugs and
develop small features. Although Google has the final say in what ends up in the Android platform, it is always
interested in high-quality features that others are willing to contribute.
The first thing to do is visit the Android contribution discussion forum (https://groups.google.com/
forum/?fromgroups#!forum/android-contrib) and describe your new proposal there. If you want to fix a bug, visit the issue tracker for the Android platform (https://code.google.com/p/android/
issues/list) and report it there (don’t forget to check if it has already been reported!). When you feel comfortable that your proposed fix is okay, you can start your contribution process.
Contributions to one of the Android projects are described in detail at http://source.android.com/
source/contributing.html. Remember that your fix needs to maintain high-quality standard and conform to the Android Code Style Guidelines (see http://source.android.com/source/code-
style.html). Don’t expect that your contribution will get accepted right away or that the processing will go fast. Google deals with a huge number of contributions, and most never make it into the platform for various
reasons. Be patient and make sure that your code is well-tested and documented.
Figure 16-3 shows the workflow of a patch. As you can see, you must take a number of steps before your
contribution is accepted, but these steps help to guarantee that your code and feature is of high quality.
Image reproduced from work created and shared by the Android Open Source Project (http://source.android.com/source/life-of-a-patch.html)
and used according to terms described in the Creative Commons 2.5 Attribution License.
Figure 16-3 The Android Open Source Project Contribution Workflow
Summary
In this chapter, I explained the basics of developing the Android platform. I showed how you can unlock your
Google Nexus device and flash it with a custom firmware and how you can download the entire Android
platform source code and relevant binary drivers to build your own version. I explained some of the reasons for
doing so from a business perspective. By working closely with a device manufacturer, your business can get its
proprietary third-party solutions that require system-level access onto a device and still be able to upgrade the
application through the Google Play Store.
I showed how you can build a system application and push it onto the system partition to test it on your custom
firmware. Next, I explained the steps for signing an application with the platform certificate in order to gain
access to permissions requiring signature permissionLevel.
I completed this chapter with an example for modifying the Android platform by adding a new system service.
Although the example is very simple and could just as well be created as a normal application, it demonstrates
how and where you should place your new code in the Android platform.
I believe that all Android developers would benefit from a full understanding of how the Android platform is
built and works, as well as how the platform certificates protect the sensitive platform APIs.
If you come up with a new feature for the Android platform, consider contributing it back to the Android Open
Source Project. There’s no guarantee it will be accepted by Google and merged into the Android source code,
but it’s a good way to practice participating in a large open-source project. Doing so also is great experience in
developing high-quality code—because the requirements from Google are very high.
Further Resources Websites
Developing for the Android Open Source Project: http://source.android.com
The Gerrit site for reviewing patches to AOSP: https://android-review.googlesource.com
The main discussion group for Android platform and technologies: https://groups.google.com/forum/?fromgroups#!forum/android-platform
The main discussion group for help with building the Android source code: https://groups.google.com/forum/?fromgroups#!forum/android-building
The discussion group for those who want to port Android to a new device: https://groups.google.com/forum/?fromgroups#!forum/android-porting
The main discussion group for those who want to contribute to the Android platform through AOSP:
https://groups.google.com/forum/?fromgroups#!forum/android-contrib
The public issue tracker for bugs in the Android platform: https://code.google.com/p/android/issues/list
XDA Developers forums: http://forum.xda-developers.com