Debloating Android 2.3 on the HTC Desire Z: use pm disable

The careful way to strip a ROM is to delete nothing, and that is how I set out to strip CyanogenMod 7.2.0, the community build of Android 2.3, on an HTC Desire Z: a 2010 phone with a slide-out keyboard that I'm turning into a pocket terminal for a mesh network. Every unwanted system APK would move out of /system/app/ into /system/app-disabled/. Reversible, nothing deleted, every step undone with one mv.

Forty minutes into the debloat, right after I had put every moved APK back once, the setup wizard's "Welcome to HTC Vision" came back and got in the way, so I typed adb shell pm disable com.android.setupwizard. It printed Package com.android.setupwizard new state: disabled, and I went straight back to moving files. The package manager's own off switch for system apps had just worked in my terminal. Remember that line. I did, one package at a time: over the next twenty hours it got the setup wizard twice more and the stock launcher four times, and every list longer than one name still went by mv.

What follows is which system apps were safe to take out, which four I had to look at twice, and what happened between that first pm disable and the evening I finally fed it the whole list. I picked the careful method. The phone has opinions about careful.

Eighty system APKs. Three hundred seventy-seven megabytes of RAM visible to Android. One hundred thirty-eight free after boot. The Desire Z has 512MB total and CyanogenMod 7.2.0 boots like it owns all of it.

App grid on the factory-reset install, before that debloat pass took effect: 28 apps visible including FM Radio, DSPManager, Gallery, ROM Manager, Theme Chooser, Spare Parts — most of which are about to disappear

The 51 safe kills#

Fifty-one APKs came out, and only one of them, DrmProvider, left so much as a warning behind:

Google services (9 APKs, no GApps framework to miss them): BackupTransport, CalendarSync, ContactsSync, Feedback, PartnerSetup, ServicesFramework, QuickSearch, MarketUpdater, Vending.

CyanogenMod's own bloat (4): CMStats, CMUpdateNotify, RomManager, FOTAKill. FOTAKill, whose job is to kill HTC's OTA service, runs a process of its own to do it. A guard dog that also needs feeding.

Entertainment (7): Pacman (a dead downloader stub — Namco's servers are long gone, but a self-contained 1.2MB PAC-MAN 1.0.3 from 2008 lives on archive.org), Music, FM radio, DeskClock, Email, Talk, VoiceDialer.

Utilities nobody asked for (15): Calendar, CalendarProvider, Browser, Gallery3D, DSPManager, AccountAndSync, SpareParts, Development, VpnServices, GanOptimizer, MediaUploader, Protips, CarHome, SetupWizard, DrmProvider.

Cosmetic (16): CMWallpapers, LiveWallpapersPicker, MagicSmokeWallpapers, VisualizationWallpapers, ThemeChooser, ThemeManager, CMParts, Androidian, OneTimeInitializer, DownloadProviderUi, NetworkLocation, GenieWidget, HTMLViewer, LatinImeTutorial, PicoTts, TtsService.

They went out that night in batches, with reboots between rounds and three put-everything-backs along the way, and the last pass moved all 51 in one loop. CMScreenshot stayed: I wanted the screenshot tool. Free RAM after boot, the -/+ buffers column of free, went from 138MB to 190MB.

Fifty-two megabytes recovered. From a device with 512 total. That's 10% of physical memory returned by evicting wallpaper animations and the rest of the list.

Four APKs that looked inert, one that was not#

Four APKs looked like nothing and got a second look. One crashed the keyboard, two left warnings in the log, and one went back on a hunch. The first one opened my list of things that look like nothing and are holding something up. The other three only applied.

UserDictionaryProvider.apk — a content provider nobody ever opens. LatinIME, the keyboard, disagreed: with the provider gone, it crashed while loading its user dictionary, in UserDictionary.addWords called from loadDictionaryAsync. Keyboard dead. On a device where the hardware keyboard is the entire point. It went straight back.

LiveWallpapers.apk — not the picker, the base package. Without it, at boot, WallpaperManagerService tried to bind com.android.wallpaper.nexus.NexusWallpaper and logged Unknown component. Non-fatal. It went back twice that night, during the black-screen chase below and during a later scare, and stayed.

Cyanbread.apk — the CM7 theme engine's default theme. Nothing I saw failed without it. It went back during the same scare, on the guess that rendering might depend on the theme engine, and nothing settled that guess either way. It stayed, because a theme nobody notices costs less than another reboot.

DrmProvider.apk — Downloads and MMS ask for DRM permissions. Without it, PackageManager's package scan at boot logged Unknown permission for INSTALL_DRM and ACCESS_DRM. Non-fatal. Annoying if you're reading logs for real problems, and the one of the four I left out anyway.

Android 2.3's invisible dependency graph#

Android 2.3's component model is a directed graph where the edges are invisible. No manifest declares "I depend on UserDictionaryProvider." LatinIME just... calls it. At runtime, while it loads its dictionary.

The safe heuristic: content providers and services referenced by package name in other APKs' code are dependencies. Activities and receivers are almost never load-bearing — they respond to intents, they don't originate them.

To find these without decompiling everything:

# Which APKs reference a package you're about to kill? (run on host)
adb pull /system/app ./system-app
for apk in ./system-app/*.apk; do
  if unzip -p "$apk" classes.dex 2>/dev/null | strings | grep -q "UserDictionary"; then
    echo "$apk"
  fi
done

Crude. Effective. Would've saved me a reboot cycle on the keyboard crash, which is the traditional order in which I write scripts like this one.

One of that night's three black screens ended at a brightness setting — that story has its own post.

App grid partway through the debloat on the factory-reset install, status-bar clock 02:54 against 02:41 on the before shot: 24 icons; Car Home, Google Search, News & Weather, ROM Manager and Talk are gone, while Browser, Calendar, Email, Music, Spare Parts and Theme Chooser are still there

A boot hang mid-debloat, and the wipe that did not cause it#

About two hours after the last removal batch, right after I put in an SD card, the phone stuck on its boot animation: no UI, no error. A "wipe cache" from CWM (ClockworkMod) recovery changed nothing, and neither did putting every moved APK back. A factory reset was the way out. The boot hang and the reflash has the whole record.

The 29 survivors#

Twenty-nine APKs remain, and the phone has 190MB free for actual work. The full keep-list:

SystemUI, Phone, TelephonyProvider, Stk, Contacts, ContactsProvider, Settings, SettingsProvider, LatinIME, UserDictionaryProvider, Bluetooth, Camera, Mms, Calculator, Torch, SoundRecorder, FileManager, AndroidTerm, Superuser, MediaProvider, DownloadProvider, CertInstaller, PackageInstaller, DefaultContainerService, LiveWallpapers, Cyanbread, ADWLauncher, ApplicationsProvider, CMScreenshot.

MeshLauncher, my own launcher for the mesh build, was an ordinary user app that night, so it is not in the count; it moved into /system/app/ later. Sixty-four percent of the system APKs were optional. On a device with 512MB of RAM, calling that bloat is being polite.

Then the Home button stopped working#

"Reversible, not destructive" held up for a while. The debloat came with "Sorry!" crash dialogs, the keyboard's first and VoiceDialer's among them, each dismissed with a key press. Then, the night of the boot hang and after the factory reset that ended it, the Home button stopped doing anything. That post went through nine theories, and windows of crash dialogs were the last of them. Later that day a second factory reset wiped /data, Home still did nothing, and the window list after that reset held no crash dialog at all. Then came the full reflash, and on the clean install Home worked before I had disabled a single package.

So the record does not convict the moved APKs of killing Home. It does not acquit them either. What it shows is the other half: the bloat list, switched off the other way, on a phone where Home worked, and Home kept working.

The bulk list, with pm disable this time#

On the fresh install the bloat went through pm disable instead of mv, in batches of this shape:

for pkg in com.android.voicedialer com.cyanogenmod.stats com.android.protips; do
  adb shell pm disable "$pkg"
done

Three packages here; the real batches were longer, and their list was not the old one name for name: CMScreenshot, which had stayed through every mv pass, went off in one of them. Spare Parts and Dev Tools stayed on this time, because Spare Parts' battery history and process views turned out to be worth having. Some calls came back Killed instead of new state: disabled, which is the package manager's way of keeping me humble.

pm disable sets the package's enabled state to COMPONENT_ENABLED_STATE_DISABLED in /data/system/packages.xml, and the framework stops resolving intents to it. The APK stays on /system/app/, so nothing the system expects ever goes missing; nothing of the package runs. The flash space stays used. Next to 512MB of RAM, flash was never the scarce thing.

After the batches, Home and the lock screen still worked, and still worked after a reboot. Then the numbers:

$ adb -s <SERIAL> shell cat /proc/meminfo | head -4 && echo "---" && adb -s <SERIAL> shell ps | wc -l && echo "processes"
MemTotal:         376792 kB
MemFree:          192640 kB
Buffers:           10660 kB
Cached:            94072 kB
---
105
processes

192MB in MemFree. Tempting to set it against the 190MB from the first night and call it a draw, and wrong: that one was the -/+ buffers column of free, which adds buffers to free memory, on the old install. Two measurements, two installs, two definitions of "free". I am not running that contest.

APK removal vs pm disable#

Moving APKspm disable
Undoremount /system, mv back, rebootpm enable <package>
Home buttonstopped working on the old install; cause never settledworked before the batches and after a reboot
Measured free RAM190MB, -/+ buffers, first night192MB MemFree, reflashed phone

The list of 51 safe kills was right about which packages could go. The method is the part I would change, and the reason is not a verdict from the record. I had the package manager's own reversible switch in my terminal forty minutes into the first night, kept using it on single packages, and kept moving files for every list because the plan said so. Being sure of yourself on Android 2.3 is free. Staying sure after the phone has shown you the better tool is the expensive part, and I paid for it one mv and one reboot at a time.

Filed under: android, hardware

Sources#

  • HTC Desire Z (vision), serial <SERIAL>, CyanogenMod 7.2.0, kernel 2.6.35.14-cyanogenmod — direct device testing
  • Android 2.3 PackageManagerService — COMPONENT_ENABLED_STATE_DISABLED flag behavior
  • pacman by namco 2008 — archive.org item holding PacMan 1.0.3.apk (1,215,585 bytes)