CLIENT_ANDROID.md 14 KB

AIM for Android Client Setup

This guide explains how to run the stock AIM for Android client (com.aol.mobile.aim 1.10.9.1, released April 2011) against Open OSCAR Server, using an Android 2.3.3 emulator on Intel macOS.

Unlike the desktop AIM clients, this one has no server settings screen. It talks to hardcoded AOL hostnames, so getting it connected means redirecting those hostnames to your machine and teaching the emulator to trust your server's certificate.

Why API 10, and why Intel

The APK declares minSdkVersion=3 (Android 1.5) and targetSdkVersion=4 (Android 1.6). It ships no native libraries, and its manifest sets supports-screens anyDensity="false" with largeScreens="false" — it expects a small, mdpi screen. API 10 (Android 2.3.3 Gingerbread) is the oldest system image Google still publishes for x86, and it was the current release the week this APK was built, which makes it the closest thing to what the client was actually tested against.

This guide requires an Intel Mac. x86 system images below API 21 cannot run on Apple Silicon — the emulator exits with Avd's CPU Architecture 'x86' is not supported by the QEMU2 emulator. On Apple Silicon the oldest usable image is system-images;android-21;default;arm64-v8a, and the CA installation in step 7 is different there (Android 4.0+ has a user certificate store; Gingerbread does not).

Prerequisites

  • Docker Desktop, for running the server (see DOCKER.md)
  • A JDK, for keytool
  • The Android command line tools and platform tools

    brew install --cask temurin@21
    brew install android-commandlinetools android-platform-tools
    

You will also need the APK itself, which is archived on APKMirror among other places.

1. Set up the SDK environment

Homebrew does not put the SDK binaries on your PATH. Add them to your shell, or source them per session:

export ANDROID_HOME=/usr/local/share/android-commandlinetools
export PATH="$ANDROID_HOME/emulator:$ANDROID_HOME/platform-tools:$ANDROID_HOME/cmdline-tools/latest/bin:$PATH"

On Apple Silicon the Homebrew prefix is /opt/homebrew instead of /usr/local.

Verify:

adb version
sdkmanager --version

2. Create the emulator

sdkmanager "system-images;android-10;default;x86" "platforms;android-10" emulator platform-tools

avdmanager create avd -n aim_api10 \
  -k "system-images;android-10;default;x86" \
  -d "3.2in HVGA slider (ADP1)"

platform-tools has to be installed here even though brew install android-platform-tools already put adb on your PATH. The emulator decides whether a directory is a valid SDK root by looking for a platform-tools subdirectory inside it, and Homebrew installs to /usr/local/bin instead. Without the sdkmanager copy the emulator refuses to start:

WARNING | platform-tools subdirectory is missing under /usr/local/share/android-commandlinetools
FATAL   | Cannot find AVD system path. Please define ANDROID_SDK_ROOT

avdmanager prints Error: Could not load devices from .../devices.xml while creating the AVD. That one is harmless — the device profile is still applied. Confirm with grep hw.lcd ~/.android/avd/aim_api10.avd/config.ini, which should report 320x480 at density 160.

The 3.2in HVGA slider (ADP1) profile is 320x480 at mdpi, which is exactly the screen the manifest asks for. On a larger or denser profile the app is bitmap-stretched into screen compatibility mode, which is a rendering path that was never tested in 2011.

3. Boot with a writable system partition

emulator -avd aim_api10 -writable-system -no-snapshot-save -gpu swiftshader_indirect -no-boot-anim

-writable-system is required. Steps 6 and 7 modify /system, and without this flag the emulator boots a pristine system.img on every launch and those edits are silently discarded.

-gpu swiftshader_indirect is needed because a 2011 system image predates the emulator's default graphics backend. If the emulator still fails to boot, try -gpu off.

Confirm it came up:

adb wait-for-device
adb devices

4. Start the server

Follow DOCKER.md for the full walkthrough. The short version:

make docker-images
make docker-cert OSCAR_HOST=ras.dev
make docker-run OSCAR_HOST=ras.dev

No extra certificate configuration is needed for this client. The certificate make docker-cert generates already carries a subjectAltName covering api.oscar.aol.com, api.screenname.aol.com, login.oscar.aol.com and my.screenname.aol.com. Only the first two and my.screenname.aol.com actually appear in the APK — login.oscar.aol.com is not in the dex at all, and is covered here and in step 6 only because it costs nothing. The matching root certificate lands at certs/ca.crt, and you install it on the emulator in step 7.

For reference, this is what the client calls:

Purpose URL Protocol
auth/clientLogin https://api.screenname.aol.com/ HTTPS — requires the root CA
aim/startSession, aim/fetchEvents, etc. http://api.oscar.aol.com/ plain HTTP

Setting DISABLE_AUTH=true will auto-create accounts on first sign-in, which saves a trip through the management API while you are getting set up. Set it in whichever settings file you actually launch with — config/ssl/settings.env for the SSL/nginx setup this guide uses, config/settings.env for the plain one.

5. Register the client's API key

The Web API requires a developer key in the k query parameter. This client has its key hardcoded to ao1BsrrRGV1M-xS4, and cmd/webapi_keygen only mints random keys, so the row has to go in by hand:

sqlite3 oscar.sqlite <<'SQL'
INSERT INTO web_api_keys
  (dev_id, dev_key, app_name, created_at, is_active, rate_limit, allowed_origins, capabilities)
VALUES
  ('dev_aim_android', 'ao1BsrrRGV1M-xS4', 'AIM for Android', strftime('%s','now'), 1, 600, '[]', '[]');
SQL

Requests that carry an a= auth token are accepted even when the key is unknown, but the endpoints that authenticate on k alone return 403 invalid API key without this row.

6. Redirect the AOL hostnames

The emulator reaches your Mac's loopback address at 10.0.2.2. Point the hardcoded hostnames there.

Android 2.3 has no sed -i, so pull the file, edit it on the host, and push it back:

adb root
adb remount

adb pull /system/etc/hosts .
cat >> hosts <<'EOF'
10.0.2.2  api.oscar.aol.com
10.0.2.2  api.screenname.aol.com
10.0.2.2  login.oscar.aol.com
10.0.2.2  my.screenname.aol.com
EOF
adb push hosts /system/etc/hosts

Verify:

adb shell ping -c1 api.oscar.aol.com

7. Install the root CA

Android 4.0 and later have a user certificate store and a Settings → Security → Install certificate flow. Gingerbread has neither. Its entire trust store is one BouncyCastle keystore at /system/etc/security/cacerts.bks, and the only way to add a CA is to merge it into that file.

There is no way around this: the client installs no custom TrustManager, so an untrusted CA means clientLogin fails TLS validation and sign-in hangs on a spinner forever.

curl -O https://repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk18on/1.78/bcprov-jdk18on-1.78.jar

adb pull /system/etc/security/cacerts.bks .

keytool -J-Dorg.bouncycastle.bks.enable_v1=true \
  -importcert -v -trustcacerts -alias openoscarca \
  -file certs/ca.crt \
  -keystore cacerts.bks \
  -storetype BKS-V1 \
  -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider \
  -providerpath bcprov-jdk18on-1.78.jar \
  -storepass changeit

Three things to watch for:

  • -J-Dorg.bouncycastle.bks.enable_v1=true is not optional on modern BouncyCastle. Since 1.71 the provider registers the BKS-V1 keystore type only when that system property is set — BC$Mappings.configure gates the addAlgorithm("KeyStore.BKS-V1", ...) call behind Properties.isOverrideSet("org.bouncycastle.bks.enable_v1"). Without it the import fails immediately with java.security.KeyStoreException: BKS-V1 not found. The -J prefix is how keytool forwards a flag to its own JVM.
  • -storetype BKS-V1 is not optional either. BouncyCastle 1.47 and later default to writing BKS v2, which Gingerbread cannot parse.
  • changeit is the stock password on the shipped cacerts.bks.

Verify the result before pushing it. The entry count should go up by exactly one, and the first four bytes of the file should still read 00000001 — that is the BKS format version, and a 00000002 there means you have a v2 keystore that Gingerbread will not load:

keytool -J-Dorg.bouncycastle.bks.enable_v1=true -list \
  -keystore cacerts.bks -storetype BKS-V1 \
  -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider \
  -providerpath bcprov-jdk18on-1.78.jar \
  -storepass changeit | grep -c trustedCertEntry     # 127 before, 128 after

xxd -l 4 cacerts.bks                                 # 00000000: 0000 0001

Once it checks out, push it back:

adb root
adb remount
adb push cacerts.bks /system/etc/security/cacerts.bks
adb shell chmod 644 /system/etc/security/cacerts.bks

Then restart the emulator so the new trust store is read at boot. Do not use adb reboot — on this image it shuts the emulator down instead of restarting it, leaving the process dead and adb devices empty. Kill it and boot it again:

adb emu kill
emulator -avd aim_api10 -writable-system -no-snapshot -gpu swiftshader_indirect -no-boot-anim

Nothing is lost in the process. -writable-system keeps your /system edits in system.img.qcow2 inside the AVD directory, so both the hosts file and the trust store survive a cold boot as long as every launch passes the flag.

You can sanity check the certificate chain from the host before going back to the emulator:

curl --cacert certs/ca.crt https://api.screenname.aol.com/auth/clientLogin

8. Install the client

adb install -r "com.aol.mobile.aim_1.10.9.1-138_minAPI3(nodpi).apk"

Launch it from the app drawer, or:

adb shell am start -n com.aol.mobile.aim/com.aol.mobile.aim.ui.AimActivity

Sign in with any screen name and password. With DISABLE_AUTH=true the account is created on first use.

9. Verify

Watch the client:

adb logcat | grep -i aim

Watch the server. A successful sign-in produces /aim/startSession followed by a steady stream of /aim/fetchEvents long-polls. The nginx access log records the negotiated TLS parameters per request:

ssl=TLSv1/RC4-MD5

TLSv1 is expected here — Gingerbread predates TLS 1.2 — and the client negotiates an equally period-appropriate cipher; RC4-MD5 and AES128-SHA are both normal. The ras-nginx:1.28.0-openssl-1.0.2u image this project pins exists precisely to keep those protocols and ciphers available, so no configuration change is needed.

Only auth/clientLogin goes over TLS. Everything after it is plain HTTP on port 80 and logs as ssl=-/-, so a successful run looks roughly like this:

"POST /auth/clientLogin HTTP/1.1" 200 ssl=TLSv1/RC4-MD5
"GET /aim/startSession?a=...&k=ao1BsrrRGV1M-xS4... HTTP/1.1" 200 ssl=-/-
"GET /aim/fetchEvents?aimsid=...&seqNum=0&timeout=180000 HTTP/1.1" 200 ssl=-/-

fetchEvents long-polls with timeout=180000, so once the client settles an idle session produces one request every few minutes rather than a fast stream. A rising seqNum is the thing to watch.

Troubleshooting

Avd's CPU Architecture 'x86' is not supported by the QEMU2 emulator

You are on Apple Silicon. x86 images below API 21 will not run there; use system-images;android-21;default;arm64-v8a or newer instead.

The hosts file or certificate reverts after a reboot

The emulator was started without -writable-system. Every launch needs the flag, not just the one where you made the edits.

keytool error: java.security.KeyStoreException: BKS-V1 not found

The -J-Dorg.bouncycastle.bks.enable_v1=true flag is missing. See step 7.

Cannot find AVD system path. Please define ANDROID_SDK_ROOT

platform-tools is not installed inside the SDK root. Run sdkmanager "platform-tools" — the Homebrew formula alone does not satisfy this. See step 2.

Sign-in shows a spinner that never resolves

The root CA is not trusted. Confirm the store you pushed is v1 and contains your CA, using the two verification commands at the end of step 7. A v2 keystore loads as empty on Gingerbread and every TLS connection fails.

adb devices is empty after adb reboot

adb reboot shuts this image down rather than restarting it. Relaunch the emulator; see step 7.

The emulator will not boot

A 2011 system image against a current emulator release is a long reach. Try -gpu off, and if that fails, install an older emulator package through sdkmanager.

adb shell input keyevent 82 opens the menu once, then stops working

Dismiss the menu with the back key before sending the menu key again:

adb shell input keyevent 4

Error: Unknown command: tap

API 10's input only implements text and keyevent; tap arrives in a later release. Drive the UI with the D-pad instead — 19 up, 20 down, 21 left, 22 right, 23 select, 4 back. The sign-in screen is reachable that way: 19 to the username field, type, 20 to the password field, type, then 20 20 23 to press Sign In.

adb shell screencap is not found

Also missing on API 10. Reading /dev/graphics/fb0 is not a workaround either — under QEMU2 it returns all zeros whatever -gpu mode you boot with. Take screenshots through the emulator console, which writes a PNG to a directory on the host:

mkdir -p "$PWD/shots"
adb emu screenrecord screenshot "$PWD/shots"

The path must be absolute. The emulator resolves a relative one against its own working directory, not your shell's, and still answers OK — so a relative path looks like it worked while writing nothing where you expected it.