Dimitry Ivanov

Enhance Android source code for developers

by DIMITRY IVANOV

It's actually a follow-up of the previous @since tag to Android source distribution. Turned out this can be done by ourselves (plus a bit more). Here is the final result:

/**
 * Called by the system when the activity changes from fullscreen mode to multi-window mode and
 * visa-versa.
 *
 * @see android.R.attr#resizeableActivity
 * @param isInMultiWindowMode True if the activity is in multi-window mode.
 * @deprecated Use {@link #onMultiWindowModeChanged(boolean, Configuration)} instead.
 * @since 7.0 Nougat (24)
 * @deprecated 8.0 Oreo (26)
 */
@Deprecated
public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
    // Left deliberately empty. There should be no side effects if a direct
    // subclass of Activity does not call super.
}

TLDR: Enhance!

I decided to investigate if it's possible to obtain the API versions information somehow. For starters, documentation reference has it. So there must be a way to extract it. I checked the source code and found out that version info is hardcoded into CSS classes:

<li class="api apilevel-21 absent" />

Not a very good start. And not much to do with this data even if we scrap it (packages, classes, methods, fields all must be somehow distinguished).

Well, we picked an easy target first. Let's find something of our own size. We want to enhance experience of a developer when working with an IDE ( an? ;) ) so API version information is easily available when navigating through source code. Is there anything in an IDE that somehow resembles this behavior? The closest (and the only one actually) is Lint and it's NewApi and Deprecated rules.

...time passed...

Turned out that Lint is using the api-versions.xml file as an API version information storage. It is located in {ANDROID_HOME}/platforms/android-{sdk}/data/. It looks like this:

<class name="android/database/AbstractCursor" since="1">
	<extends name="java/lang/Object"/>
	<implements name="android/database/CrossProcessCursor"/>
	<method name="&lt;init>()V"/>
	<method name="checkPosition()V"/>
	<method name="getNotificationUri()Landroid/net/Uri;" since="11"/>
	<method name="getUpdatedField(I)Ljava/lang/Object;" deprecated="16"/>
	<method name="isFieldUpdated(I)Z" deprecated="16"/>
	<method name="onChange(Z)V"/>
	<field name="mClosed" deprecated="23"/>
	<field name="mContentResolver" deprecated="23"/>
	<field name="mCurrentRowID" deprecated="16" removed="23"/>
	<field name="mPos" deprecated="23"/>
	<field name="mRowIdColumnIndex" deprecated="16" removed="23"/>
	<field name="mUpdatedRows" deprecated="16" removed="23"/>
</class>

It might be scary at first if you are not on good terms with Java bytecode signatures, but it's a great source of what we are looking for.

We have our data source, but let's keep it for now, as we must find a way to apply this information to the Android source code. Of cause we won't be compiling Android sources. We won't succeed anyway and what good will it give us? What we need is to find a good text processor, that will allow slight modification of some of Java code, but keeping everything else as it is.

First that comes to mind is ANTRL. But I had experience with it previously and decided to look for other available options knowing that at any moment I can use ANTRL instead if I won't find anything suitable.

...no time passed at all...

I have found a candidate for the task pretty quickly (first result in you-know-where): Javaparser. I decided to give it a go.

final CompilationUnit unit;
try {
    unit = JavaParser.parse(file);
} catch (FileNotFoundException e) {
    throw new RuntimeException(e);
}

At this point we have already an AST ready for traversal. I tried to modify some mock Java sources and it worked as expected. Now it was the time to glue the parts together.

At this point glueing would mean solving 2 problems:

  • Correctly identify traversed class

    In the api-versions.xml each class (including nested ones) are represented by independent entry, for example: android/database/AbstractCursor, android/database/AbstractCursor$SelfContentObserver

  • Correctly identify traversed methods:

    In the api-versions.xml methods are represented as they would be in Java bytecode: getUpdatedField(I)Ljava/lang/Object;, <init>(II)V

First one is easy to solve as we are processing single source file at a time. So: one file - one package; Nested classes can be declared only in the same source file. So no problem here.

Second one is a bit harder. As we are dealing with independent source files it would be extremely hard to create a signature like this: startIntentSenderFromChild(Landroid/app/Activity;Landroid/content/IntentSender;ILandroid/content/Intent;IIILandroid/os/Bundle;)V without proper symbol resolution (be it manual tracking of imports or using provided by javaparser javaparser-symbol-solver-core artifact). So I decided to tweak signature a bit to be like this: startIntentSenderFromChild(LActivity;LIntentSender;ILIntent;IIILBundle;)V (Please note that it's only for the method signatures). We exclude package information and parent info (Landroid/app/AlertDialog$Builder; -> LBuilder;). It's not impossible that there will be no collisions, but it's a sacrifice I'm willing to make.

Then I had another idea of what we can do more. As we are processing source files why not adding ... automatic code formatting? Android source code is a great example of consistency </s> when it comes to formatting, so why not adding AOSP code formatting to it? Thankfully Google has released a tool just for this task: google-java-format.

Some examples of what output look like:

UNMODIFIED:

/**
 * Create a new key event.
 *
 * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
 * at which this key code originally went down.
 * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
 * at which this event happened.
 * @param action Action code: either {@link #ACTION_DOWN},
 * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
 * @param code The key code.
 * @param repeat A repeat count for down events (> 0 if this is after the
 * initial down) or event count for multiple events.
 * @param metaState Flags indicating which meta keys are currently pressed.
 * @param deviceId The device ID that generated the key event.
 * @param scancode Raw device scan code of the event.
 * @param flags The flags for this key event
 * @param source The input source such as {@link InputDevice#SOURCE_KEYBOARD}.
 */
public KeyEvent(long downTime, long eventTime, int action,
                int code, int repeat, int metaState,
                int deviceId, int scancode, int flags, int source) {
    mDownTime = downTime;
    mEventTime = eventTime;
    mAction = action;
    mKeyCode = code;
    mRepeatCount = repeat;
    mMetaState = metaState;
    mDeviceId = deviceId;
    mScanCode = scancode;
    mFlags = flags;
    mSource = source;
}

AOSP:

/**
 * Create a new key event.
 *
 * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis}) at which this key
 *     code originally went down.
 * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis}) at which this
 *     event happened.
 * @param action Action code: either {@link #ACTION_DOWN}, {@link #ACTION_UP}, or {@link
 *     #ACTION_MULTIPLE}.
 * @param code The key code.
 * @param repeat A repeat count for down events (> 0 if this is after the initial down) or event
 *     count for multiple events.
 * @param metaState Flags indicating which meta keys are currently pressed.
 * @param deviceId The device ID that generated the key event.
 * @param scancode Raw device scan code of the event.
 * @param flags The flags for this key event
 * @param source The input source such as {@link InputDevice#SOURCE_KEYBOARD}.
 * @since 2.3 Gingerbread (9)
 */
public KeyEvent(
        long downTime,
        long eventTime,
        int action,
        int code,
        int repeat,
        int metaState,
        int deviceId,
        int scancode,
        int flags,
        int source) {
    mDownTime = downTime;
    mEventTime = eventTime;
    mAction = action;
    mKeyCode = code;
    mRepeatCount = repeat;
    mMetaState = metaState;
    mDeviceId = deviceId;
    mScanCode = scancode;
    mFlags = flags;
    mSource = source;
}

Final result:

[Enhance] processing took: 02 minutes 28 seconds

Not that bad for processing the whole Android source code (parse/modify/format/output) as I have encountered greater build times for a single Android application.


< Previous @since tag to Android source distribution
Next > Local Maven repository