# ───────────────────────────────────────────────────────────────────────────── # WebTerm :app — R8 keep rules (release + benchmark; both isMinifyEnabled = true). # # DISCIPLINE: every rule below is justified, and rules that turned out to be # UNNECESSARY are recorded as such rather than added "just in case". A superfluous # -keep silently costs size and hides real reachability problems; a MISSING one # crashes only in production. Both were checked against the merged configuration # R8 actually used, which is dumped to: # app/build/outputs/mapping//configuration.txt # Read that file before adding anything here — most of our stack already ships its # own consumer rules, and duplicating them is how this file rots. # # ALREADY COVERED BY EMBEDDED CONSUMER RULES — do NOT re-declare here: # · kotlinx.serialization — kotlinx-serialization-core-jvm ships # META-INF/com.android.tools/r8/kotlinx-serialization-{common,r8}.pro, which keeps # @Serializable classes' `Companion` fields, `serializer(...)`, object `INSTANCE`, # the `$$serializer.descriptor` field, and RuntimeVisibleAnnotations (the R8 # full-mode fix). Verified present in 1.9.0. Our @Serializable models live in # :wire-protocol / :api-client; the rules are global, so they are covered. # · Hilt / Dagger — hilt-android + dagger AARs ship proguard.txt; all injection is # generated code with compile-time references, no runtime reflection to preserve. # · Tink — tink-android ships META-INF/proguard/protobuf.pro, keeping on # subclasses of the shaded protobuf GeneratedMessageLite (the reflective lite # runtime). Tink's key managers are registered explicitly by AeadConfig.register(), # not discovered reflectively, so nothing else needs keeping. # · FCM — firebase-messaging ships consumer rules, AND `.push.FcmService`, # `.push.DenyBroadcastReceiver`, `.push.AllowTrampolineActivity`, `.MainActivity` # and `.WebTermApp` are all declared in AndroidManifest.xml, for which AGP # auto-generates keep rules (build/intermediates/aapt_proguard_file/.../aapt_rules.txt). # Manifest-declared components therefore need no rule here — ever. # · OkHttp / Okio, Compose, CameraX, ML Kit barcode, DataStore, navigation-compose, # biometric — all ship their own consumer rules. # · JNI — the default proguard-android-optimize.txt already contains # `-keepclasseswithmembernames class * { native ; }`, which covers the # Termux emulator's native entry points even though we never fork a subprocess. # ───────────────────────────────────────────────────────────────────────────── # ── Termux terminal emulator + view (the ONE thing that genuinely needs a rule) ── # # Why this is not "just in case": # 1. The JitPack artifacts (com.github.termux.termux-app:terminal-{emulator,view}, # v0.118.0) ship NO consumer rules at all — nobody upstream has reasoned about R8 # for these modules, because Termux itself ships unminified. # 2. :terminal-view deliberately binds to NON-API internals of the pinned version # (plan §6.1 — TerminalView is `public final`, so we fork rather than subclass): # · RemoteTerminalHostView writes and reads the public field # `com.termux.view.TerminalView.mEmulator` directly (installEmulator / # the TerminalViewClient callbacks / computeVerticalScrollRange). # · TerminalResizeDriver calls `TerminalRenderer.getFontWidth()` and # `getFontLineSpacing()` — public accessors verified with `javap -p`, but not # part of any documented API surface. # · TerminalScrollGesture reads `mEmulator` and drives KeyHandler. # Field/method RENAMING alone would survive that (R8 rewrites both sides), but R8 # full mode also does class merging, member inlining and access relaxation across # a library whose invariants we are already stretching. A wrong outcome here is a # crash in the app's single core surface — the terminal — visible ONLY in release. # 3. The cost is bounded and small: two Apache-2.0 modules, ~120 KB of classes. # # So: keep them whole. Revisit only with a device-verified minified build in hand. -keep class com.termux.terminal.** { *; } -keep class com.termux.view.** { *; } # ── ML Kit / Firebase component discovery (reflective no-arg constructors) ─────── # # OBSERVED, not speculative. Installing the minified `benchmark` APK on an API-35 # emulator and cold-starting it produced, with no rule here: # # W ComponentDiscovery: Invalid component registrar. # Could not instantiate com.google.mlkit.common.internal.CommonComponentRegistrar # ... at com.google.mlkit.common.internal.MlKitInitProvider.onCreate # Caused by: java.lang.NoSuchMethodException: # com.google.mlkit.common.internal.CommonComponentRegistrar. [] # (and the same for com.google.mlkit.vision.common.internal.VisionCommonRegistrar) # # Firebase's ComponentDiscovery instantiates registrars named in by # `getDeclaredConstructor()`. The class names survive, but R8 removed the no-arg # constructors because nothing in the app calls them. The failure is a WARNING, not a # crash: the app starts fine and the ML Kit component graph is simply never registered, # so **QR pairing (A19 barcode scanning) silently stops working in minified builds # only**. Exactly the release-only breakage this file has to prevent. # # `();` (no access modifier) matches any visibility, which is what # getDeclaredConstructor needs. Scoped to registrars, so it keeps ~a dozen tiny classes. -keep class * implements com.google.firebase.components.ComponentRegistrar { (); } # ── javax.naming does not exist on Android ────────────────────────────────────── # # R8 refuses to run without these two, so they are load-bearing for ANY minified build. # They are NOT a benign suppression — read this before deleting them: # # :client-tls CertificateSummary.kt uses `javax.naming.ldap.LdapName` to pull the CN # out of an RFC2253 DN. That package is JDK-only; it is absent from android.jar and # from the ART bootclasspath. So on a real device `CertificateSummaryReader.commonName` # throws NoClassDefFoundError, which is an Error and therefore slips straight through # that function's `catch (_: Exception)` guard. # # The module is pure-JVM and its unit tests run on a real JDK where LdapName DOES # exist, which is why the green JVM suite never caught it. Tracked as a handoff to # :client-tls's owner (hand-parse the DN, or catch Throwable). This -dontwarn only # stops R8 from failing the build over an unresolvable reference; it does not and # cannot fix the device behaviour. -dontwarn javax.naming.ldap.LdapName -dontwarn javax.naming.ldap.Rdn # ── Crash triage ──────────────────────────────────────────────────────────────── # Keep line numbers so release stack traces are usable, and rename the source-file # attribute to a single constant so no original filenames leak in the APK. Retrace # release traces with app/build/outputs/mapping/release/mapping.txt (ARCHIVE IT with # every distributed artifact — without it a crash report is unreadable). -keepattributes SourceFile,LineNumberTable -renamesourcefileattribute SourceFile