Android: Compiling and running a Java command-line utility
This is a failure
This post is a messy collection of things I wrote down as I tried to make a simple Java command-line (as in adb shell) utility for making a change in the device’s config settings. It’s one of several attempts I’ve made to stop the automatic hibernation of unused app, as discussion on this other post of mine.
Except for a lot of conclusions about what is impossible, this post shows how to compile and run a plain Java program that can access Android’s API functions. But be warned: This is most likely not very useful, because the better part of that API can’t be used. More on this below.
So I did manage to get the program running. It’s just that I learned that it’s useless.
So if you’re reading this because you want to do something quick and dirty utility for Android, my message is: Don’t. Fetch the source code of some boilerplate app somewhere, and take it from there.
Let’s be honest: Stopping the hibernation is by itself not reason enough for all the efforts I’ve put in, but it’s a good way to get acquainted with Android in a way that will hopefully help me to solve issues in the future.
And this is a good place to mention that neither Java nor Android is not what I do for living, so no wonder I get into nonsense.
I am however well-versed with Linux machines in general, which is why I’m inclined towards plain command-line programs. Which turns out to be a bad idea with Android, as evident from below.
The strategies
As mentioned in that post, the device’s config settings are more than apparently stored in an XML file, which isn’t really an XML file, but rather a file in ABX format, which is some special Android thing with currently no reliable toolset for manipulation.
Android’s own “settings” command-line utility allows manipulation of these settings, but specifically not the “config” namespace. So if the built-in utility won’t do that for me, how about writing a little thingy of my own?
Strategy #1 was to use the API methods of android.provider.Settings to change the setting values directly. But alas, the methods that manipulate the “config” namespace are not exposed to the API level. So they are accessible only from within Android’s core classes. Once again, mr. Android tells me not touch “config”.
Strategy #2 was to follow the SettingService class. What this service actually does, is to do the work as requested by the “settings” command-line utility. More on that below. Anyhow, the trick is that the actual access to the settings database (XML / ABX files, actually) is through the SettingsProvider class, which extends ContentProvider.
OK, so a quick word on Android Content Providers. This is an Android-specific IPC API that allows Android components (including apps) to expose information to just other components, with a noSQL-like set of methods. So the Content Provider is expected to have some kind of database with row/column tables, just like good old SQL. The most common use for this is the contact list (as in a phonebook).
The side that wants to access the data calls getContentResolver() with a URI that represents the requested database, and the ContentResolver object that is returned has methods like insert(), query(), update(), and most interestingly, call(). So by using the methods of the ContentResolver object, the requests are passed on the Content Provider on the other side, which fulfills the request and conveys the result.
So I thought I should connect with the SettingsProvider as a Content Provider, and use the same calls as in SettingService. The URI of this Content Provider isn’t given in SettingService, because it’s apparently given to the object constructor directly. A wild guess, based upon what I found in the dumpsys output, is “content://com.android.providers.settings/.SettingsProvider”. Based upon the Settings.java it’s more like just “content://settings”. Never got to try this however.
The reason I never got that far is that getContentResolver() is a method of the Context class. And that’s not a small technicality. Because a command line program of the sort I was playing around with doesn’t have a context. That’s something that apps and services have. There’s no way to create one ad-hoc, as far as I know. In the absence of a Context object, there’s no way to call getContentResolver(), and that means that there’s no access to the Content Provider. Game over.
This obstacle is probably not a coincidence either: In order to access data, the system wants to know who you are. A plain Java command-line utility has no such identity. In the context of the “config” class, it’s even more evident: Setting’s plain API doesn’t expose it, and the Content Provider API wants to know who fiddles with these variables.
The conclusion is hence that odds are that any valuable and interesting functionality will be impossible from a simple command-line utility. For this to have a chance to work, there must be an app or a service, which is what I wanted to avoid.
How does “settings” run
The “settings” command is in fact /system/bin/settings which consists of the following:
#!/system/bin/sh cmd settings "$@"
So what is “cmd”? Let’s just try:
$ adb shell cmd cmd: No service specified; use -l to list all running services. Use -w to start and wait for a service.
And using “cmd -l” indeed enlists “settings” as a service.
Same goes for “pm”, but with the “package” service instead.
So these important utilities don’t perform the action themselves. A bit like Linux’ systemd commands, they talk with a service instead. The rationale is probably the same: Not to expose the API that can touch the sensitive stuff, so only a service can do that.
Not directly related, but dumpsys is /system/bin/dumpsys, which is an ELF:
dumpsys: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /system/, BuildID[md5/uuid]=eb94567e9085216f43594a212afd2160, stripped
Installing the minimal SDK for compilation
Downloaded commandlinetools-linux-8512546_latest.zip from Android Studio’s download page. Android Studio can be downloaded on the same page, but I’m a little fan of IDEs, and what I want to do is small and non-standard (Gradle is often mentioned in the context of building from command line, but that’s for full-blown apps).
Unzip the file, and move the lib and bin directories into the following directory structure:
├── sdk │ └── cmdline-tools │ └── latest │ ├── bin │ └── lib
Or sdkmanager will complain that
Error: Could not determine SDK root. Error: Either specify it explicitly with --sdk_root= or move this package into its expected location: <sdk>/cmdline-tools/latest/
A little silly obstacle to prepare you for what’s ahead.
Change directory to sdk/cmdline-tools/latest and get a list of installable packages:
$ bin/sdkmanager --list
And then, to download the build tools, go something like:
$ bin/sdkmanager 'build-tools;28.0.0'
Note that the exact version is specified (they were all listed before) and that the single-quote is required because of the semicolon. Expect a long download.
Remember that old build tools are better if you don’t need the features of the new ones. Generally speaking, old tools build things that are compatible to a wider range of targets.
Congrats, the sdk/ directory now consumes 1.1 GB, most of which is under emulator/ (just delete that directory).
dx is in build-tools/28.0.0/.
Next up, obtain android.jar, which contains the API stubs. This is needed by javac, or else it refuses to import Android libraries. First, figure out which API level I need. My phone runs Android 12, so I’ll go for API level 31, according to the list at the top of this page.
Hence:
$ bin/sdkmanager 'platforms;android-31'
The SDK just grew to 1.3 GB. Nice. The important part is platforms/android-31/android.jar, and it’s there.
Like any jar file, it’s possible to peek inside with
$ jar xvf android.jar
and that allows seeing what is exposed in each class by using e.g. to see what android.provider.Settings imports:
javap android/provider/Settings.class
Note that classes defined inside classes are put in separate .class files, e.g. Settings$System.class. The first line in javap’s output says which .java file the .class file was compiled from.
Compile command line utility for Android
You know that this is probably useless, right? You’ve read the introduction above?
So let’s take this simple program as hello.java:
import java.io.*;
public class hello {
public static void main(String[] args) {
PrintStream p = java.lang.System.out; // Keep it short
p.println("Hello, world");
}
}
The only thing worth mentioning is that @p is used instead of java.lang.System.out. Just System.out is ambiguous, so it has to be this long.
Compile with
./compile.sh
where compile.sh is
#!/bin/bash set -e TARGET=hello SDK=/path/to/sdk DX=$SDK/build-tools/28.0.0/dx JAR=$SDK/platforms/android-31/android.jar rm -f $TARGET.class $TARGET.dex javac -source 1.7 -target 1.7 -bootclasspath $JAR -source 1.7 -target 1.7 hello.java $TARGET.java $DX --dex --output=$TARGET.dex $TARGET.class
The -source 1.7 -target 1.7 thing in javac’s arguments is to create Java 7 bytecode. Which is the reason for this warning, which can be ignored safely:
warning: [options] bootstrap class path not set in conjunction with -source 7
This is a good time to mention that Java is deprecated, and Android embraces Kotlin. As if they didn’t learn the lesson with those .NET languages.
If everything goes fine, send it to the phone (delme-tmp should be created first, of course):
$ adb push hello.dex /storage/emulated/0/delme-tmp/
Execution: Change directory to /storage/emulated/0/delme-tmp and go
$ dalvikvm -Djava.class.path=./hello.dex hello Hello, world
Yey. It works. But that’s hardly worth anything, is it? How about using some Android API? So let’s change hello.java to this. The most basic thing I could think about (and in fact, I failed to come up with anything to do that didn’t somehow require a context):
import java.io.*;
import android.os.Build;
public class hello {
public static void main(String[] args) {
PrintStream p = java.lang.System.out; // Keep it short
String manufacturer = Build.MANUFACTURER;
p.println("Manufacturer is " + manufacturer);
}
}
But that gave
$ dalvikvm -Djava.class.path=./hello.dex hello Exception in thread "main" java.lang.UnsatisfiedLinkError: No implementation found for java.lang.String android.os.SystemProperties.native_get(java.lang.String, java.lang.String) (tried Java_android_os_SystemProperties_native_1get and Java_android_os_SystemProperties_native_1get__Ljava_lang_String_2Ljava_lang_String_2) at android.os.SystemProperties.native_get(Native Method) at android.os.SystemProperties.get(SystemProperties.java:165) at android.os.Build.getString(Build.java:1434) at android.os.Build.<clinit>(Build.java:54) at hello.main(hello.java:8)
So compiling against the libraries is fine, but actually getting it to do something is a different story. There is probably a solution for this, but given the prospects of getting something useful done with this method, I didn’t bother going any further.
Sources on compilation:
- See the last answer on this page on how to compile something that runs with DalvikVM.
- See this page on how to compile a command-line program, albeit with a shell wrapper for “app_process”. This page also mentions that both dalvikvm as well as app_process lack context.
General notes
Most important: There’s Android Code Search for looking up stuff in the code base.
The sources for the Android core functions can be fetched with
git clone https://github.com/aosp-mirror/platform_frameworks_base.git
It’s 4.6 GB, so it takes quite some time to download.
It’s a good idea to check out a tag that corresponds to the same API as in the android.jar you’re working with. For the sake of seeing the actual implementation of the API functions, as well as how they’re really used, when the API was in that stage. I haven’t found a clear-cut way to connect between the API level and a tag in the git repo, so I went by the dates of the commits.
- The root of the core classes is at platform_frameworks_base/core/java
- There’s a direct connection between the class name and the directory it resides under.
- The package directive at the beginning of Java files are just a way to allow wildcards for importing several classes at once.
- Command-line utilities tend to extend the ShellCommand class. Looking for this is a good way to find the utilities and test programs in the source code.
- However a common way to implement a command-line utility is through the “cmd” executable, which invokes the onShellCommand() public method of a service.
Dissection notes
Random pieces of info as I went along (towards failure, that is):
- setSyncDisabledMode() is defined in core/java/android/provider/Settings.java, inside the Config subclass (search for “class Config”) in the file.
- The command-line utility “settings” is implemented in packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java.
- The Settings functionality is implemented in packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java. The most interesting part is the list of methods it supports, under the “call” method, which includes constants such as Settings.CALL_METHOD_LIST_GLOBAL (accessed by the “settings” utility) but also Settings.CALL_METHOD_LIST_CONFIG (which isn’t accessed by the same utility).
- The names of the XML files are listed in packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java (e.g. SETTINGS_FILE_CONFIG = “settings_config.xml”).
It’s quite apparent that the command-line utility (SettingsService.java) uses the public method calls (e.g. Settings.CALL_METHOD_GET_SYSTEM) and it doesn’t seem like there’s another way to access the registery (i.e. the XML files) because calls a private method for implementing this call (getSystemSetting() in this case).
Reader Comments
Use app_process not dalvikvm