P Luthfi's Playground

Field report · Wear OS teardown · 2026-11-02

Galaxy Watch 4 debloat: cleaner, not faster

Seventy-three packages removed, 300 MB of logs swept, animation scale halved. The watch is measurably cleaner and still swaps 40% of its ZRAM. This is the record of what was cut, what it bought, and the two readings that were misinterpreted along the way.

Done Root cause unresolved wear-os adb performance memory
Device
Galaxy Watch 4, 44mm BT (SM-R870)
SoC / RAM
Exynos W920, 2 cores · 1376 MB
Platform
Android 16 / SDK 36
Build
R870XXS2JZG3
Transport
ADB over Wi-Fi
Goal
Fix lag and ~5 min cold boot
TL;DR
  1. Debloat worked as cleanup (270 → 197 packages, −400 MB storage) but did not fix the lag: swap stayed at ~40%.

  2. The CPU was 88% idle. High load average was processes waiting on ZRAM swap-in, not compute.

  3. Swap is spread across ~18 core processes. Android 16 simply does not fit in 1.4 GB; there is no rogue app to remove.

  4. One real defect found: a hidden watch face holding 252 MB at oom_score_adj 0. Removed.

  5. Best fix for the original complaint: never let the watch power off, so the slow cold boot never happens.

Outcome

Everything was executed over ADB against the live device. The cleanup succeeded; the problem it was meant to solve did not move.

Swap in use 40% of 963 MB ZRAM
was 42% · unresolved
Packages 197 from 270 −73
73 removed · all reversible
Storage free 6.1 GB of 8.3 +900 MB
was 5.2 GB
CPU idle 88% across 2 cores
never the bottleneck
MetricBeforeAfterRead
Installed packages270197−73
Watch faces602active + emergency
Storage used2.9 GB2.5 GB−400 MB
/data/log265 MB2.2 MB−263 MB
Animation scale1.00.5perceptible
Swap in use42%40%within noise
MemAvailable381 MB306–366 MBwithin noise
Before → after, measured on device. Grey rows moved less than the ±30 MB idle noise band.
Hypothesis 1

Removing bloat apps will free enough RAM to fix the lag.

Rejected swap 42% → 40%, within noise
Hypothesis 2

Load average 6.9 on 2 cores means the CPU is saturated.

Rejected top: 176% idle of 200%
Hypothesis 3

Lag is swap-in stall: the OS does not fit in 1.4 GB RAM.

Supported 417 MB swapped, spread over core processes
Hypothesis 4

Some hidden process holds memory it should not.

Supported healthmodular, 252 MB at priority 0

Diagnosis: load average lied, swap told the truth

A one-minute load average of 6.9 on a dual-core chip reads like a CPU pinned flat. It was taken as exactly that, and that reading was wrong. One command settled it.

adb shell top -b -n 1the decisive readout
Tasks: 379 total,   1 running, 378 sleeping,   0 stopped,   0 zombie
 Mem:  1409864K total,  1344132K used,    65732K free,     1004K buffers
Swap:   986900K total,  417056K used,   569844K free,   345732K cached
200%cpu   9%user   0%nice  12%sys  176%idle   0%iow   3%irq   0%sirq

176% idle out of 200%. One task running out of 379. The CPU was asleep. Linux load average counts processes blocked in uninterruptible sleep alongside runnable ones, and those processes were blocked on ZRAM decompression, not on the scheduler. The lag was swap-in stall, and the 417 MB on the swap line is the whole story.

Swap was spread evenly across roughly eighteen processes at 28–50 MB each. Nine of the top ten are untouchable.

That distribution is the finding. There is no misbehaving app to evict — this is Android 16 not fitting inside 1.4 GB on 2021 silicon. The two removable entries total 75 MB and are both in daily use.

Wrong instrument, corrected

MemAvailable swings ±30 MB within a minute on an idle device (351 → 335 → 366 MB). Single snapshots prove nothing. Swap is the real signal.

VmSwap per process, top 10 (MB)

  • com.google.android.gms
    50.8 MB
  • …apps.wearable.settings
    49.4 MB
  • system_server
    49.2 MB
  • …watchface.dynamicfont
    45.1 MB
  • …wearable.sysui
    42.1 MB
  • …wearable.assistant
    42.1 MB
  • …apps.wearable.systemui
    39.1 MB
  • com.google.wear.services
    36.6 MB
  • …wear.shealth
    32.6 MB
  • …providers.media.module
    32.3 MB
  • core (not removable)
  • active watch face
  • removable, but in daily use

Scale 0–60 MB.

The one real catch

A dormant watch face holding 252 MB at priority zero

Removing the third-party watch faces exposed something the first sweep had hidden. healthmodular, a preinstalled face that was not selected and not visible, was running a foreground service with thirty threads.

adb shell dumpsys activity oombefore removal
Proc # 6: fg  b/ /SVC  t: 0 8508:...watchface.healthmodular (exec-service)
          RSS 251888 kB    oom_score_adj 0    threads 30
compare — a genuinely idle face:
Proc #12: cch+ 5 b/ /CEM  t: 0 9152:...watchface.mystyle (cch-empty)
          RSS   52436 kB    oom_score_adj 905    threads 25

healthmodular · not active

Bug
RSS
252 MB
oom_score_adj
0 (foreground)
Threads
30

mystyle · idle

Normal
RSS
52 MB
oom_score_adj
905 (cached empty)
Threads
25

oom_score_adj = 0 places a process on par with the active foreground app: under memory pressure the kernel will not reclaim it. Its neighbour at 905 is discarded the instant anything needs the pages. This was the only genuine defect found all session, and it is gone. Its effect shows up under pressure rather than at idle, which is exactly where an idle snapshot cannot see it.

How the investigation actually ran

Including the detours. Two measurements were read wrong before the right instrument was found, and both misreads pointed at work that turned out to be worthless.

  1. Connect over Wi-Fi

    Wireless debugging on Wear OS uses Android 11-style pairing: a random port and a six-digit code, distinct from the connect port. Pairing persists across reboots; the port does not, and wireless debugging switches off on every restart.

  2. First reading: MemFree at 76 MB Wrong metric

    Interpreted as acute RAM starvation. The top-RSS table was used to rank removal candidates, which put Play Store at 152 MB in first place.

  3. Fourteen packages removed, then a reboot No effect

    Play Store, Bixby, Smart Switch, diagnostic telemetry, Knox EMM, Global Goals, two leftover test faces. MemAvailable moved from 381 MB to 383 MB. Play Store and the FOTA agent were restored.

  4. Watch faces: 58 removed, one defect exposed Real finding

    Each candidate was checked for a WallpaperService before deletion, which correctly spared tickingsound (not a face) and emergency (the low-power emergency display). Removing the rest surfaced healthmodular.

  5. Second reading: load average 6.9 / 8.2 / 10.1 Wrong conclusion

    Interpreted as a saturated dual-core CPU. It was not. top showed 88% idle in the same minute; the queue was swap-wait, not compute.

  6. Sampling MemAvailable three times, 20 s apart Instrument corrected

    351 MB, then 335 MB, then 366 MB: a ±30 MB swing on an untouched device. Every single-snapshot comparison made earlier fell inside that band and proved nothing.

  7. Cache and log sweep Storage only

    /data/log held 265 MB. easyMover held 222 MB of orphaned data (every removal used -k to stay reversible, which keeps app data behind). Reclaimed roughly 300 MB. Swap did not move.

  8. Stop

    Nothing further is worth removing. The remaining consumers are the operating system.

Ledger: what went and what stayed

Every removal used pm uninstall -k --user 0, which detaches the package from the user profile while leaving the APK on the system partition. Nothing here needs a factory reset to undo (restore.sh).

Removed

73 packages · reversible
com.google.android.apps.maps
com.google.android.apps.youtube.music
com.microsoft.office.outlook
com.samsung.android.bixby.agentBixby
com.samsung.sreeGlobal Goals
com.samsung.sree.classicGlobal Goals
com.samsung.sree.countdownGlobal Goals
com.samsung.sree.digitalGlobal Goals
com.samsung.sree.spinGlobal Goals
com.sds.emm.cloud.knox.samsungenterprise MDM
com.sec.android.diagmonagenttelemetry
com.sec.android.easyMoverSmart Switch
com.whatsapp
60 watch face packages (58 in the face sweep + 2 test faces)
  • …watchface.activitynumber
  • …watchface.analogmodular
  • …watchface.analoguefont
  • …watchface.analogutility
  • …watchface.animal
  • …watchface.aremoji
  • …watchface.basicclock
  • …watchface.basicdashboard
  • …watchface.bespoke
  • …watchface.bitmoji
  • …watchface.boldindex
  • …watchface.breathe
  • …watchface.casualminimalist
  • …watchface.classic
  • …watchface.classicadventurer
  • …watchface.digitalfont
  • …watchface.digitalmodular
  • …watchface.digitalneon
  • …watchface.dualwatch
  • …watchface.endangeredanimal
  • …watchface.face
  • …watchface.fitfuture
  • …watchface.fitnesscentric
  • …watchface.flower
  • …watchface.fresh
  • …watchface.frontier
  • …watchface.gradientfont
  • …watchface.healthdashboard
  • …watchface.healthmodular
  • …watchface.infomodular
  • …watchface.intrepid
  • …watchface.large
  • …watchface.livewallpaper
  • …watchface.modernminimalist
  • …watchface.mypebble
  • …watchface.myphoto
  • …watchface.mystyle
  • …watchface.photosticker
  • …watchface.premiumanalog
  • …watchface.quadhive
  • …watchface.rosegold
  • …watchface.simpleanalogue
  • …watchface.simplebasic
  • …watchface.simpleclassic
  • …watchface.simplecomplication
  • …watchface.simpledigital
  • …watchface.sleepcoaching
  • …watchface.solariboard
  • …watchface.spatialnumber
  • …watchface.spherenumber
  • …watchface.stretchindex
  • …watchface.superfiction
  • …watchface.together
  • …watchface.tomcat
  • …watchface.typography
  • …watchface.ultrainfoboard
  • …watchface.weather
  • com.watchfacestudio.Test_23924081946917534797456318269746
  • com.watchfacestudio.Test_23924081946917536609753408129222
  • de.mindfabrik.watchface.android.dos

Kept deliberately

each for a stated reason
com.android.vendingsole installer on device (Play Store)
com.wssyncmldmfirmware OTA agent
…wear.shealthsensor stack + history
…wearable.assistantin daily use
…watchface.emergencyemergency display
…watchface.tickingsoundnot a face
complicationhelperinfrastructure
companionhelperinfrastructure
wear.watchface.runtimeinfrastructure
Consequence worth knowing

Both update paths were removed mid-session and both were restored. Without com.wssyncmldm the watch receives no firmware or security updates. Without Play Store on the watch, apps cannot be installed or updated, including from the phone: the phone only sends the request, the watch-side installer executes it.

Recommendation

Two options remain, and the first one is the owner's own.

Recommended 01

Never let it power off

Wear OS cold boot is inherently slow on this hardware: re-sync to the phone, re-index, restart every service. No amount of debloating shortens it. Enable Battery Protection at an 85% ceiling, charge regularly, never reach 0%. The watch never boots, so the five-minute wait never happens.

02

Factory reset

Not to remove apps, but to drop four or five years of accumulated app data and profiles that pm clear cannot reach on an uninstalled package. The only remaining lever with real odds, at the cost of a full re-pair and re-setup.

03

Not recommended

Clearing Galaxy Store (114 MB), GMS (110 MB) or TTS (53 MB) data. All storage-only wins on a device with 6.1 GB free; clearing GMS risks breaking account sync. Removing Samsung Health would take the sensor history with it.

Reconnecting

Wireless debugging turns itself off after every reboot. Pairing from this session is already stored, so step 2 is usually unnecessary.

  1. Enable wireless debugging on the watch

    Settings › Developer options › Wireless debugging › on

  2. Pair (once)
    adb pair <ip>:<pairing-port> <6-digit-code>
  3. Connect
    adb connect <ip>:<connect-port>   # or let mDNS find it
  4. Check the watch
    ./watchstat.sh -w   # RAM / swap / load / disk / battery, refresh 5s

Files

Raw artifacts in this folder. Captured during the session; don't edit.