R8 (minify + resource-shrink) flipped on for BOTH debug AND release
variants — we publish the debug APK to fdroid (per existing
pipeline), and the audit-flagged Log.d strip discipline required R8
to actually run on the variant we ship.
New strawApp/proguard-rules.pro covers:
* UniFFI bindings (uniffi.strawcore.*) — reflective FFI dispatch
from Rust side, must survive minification
* JNA — Library subclasses reflectively loaded by name
* kotlinx-serialization @Serializable — generated $$serializer
companions, kept via both the package-anchored rule and the
annotation-wildcard rule for belt + suspenders
* Media3 session Parcelables (cross-process via Binder)
* Compose runtime + Strawcore exception hierarchy
Surface-handoff polish on inline ↔ fullscreen transitions:
setKeepContentOnPlayerReset(true) on both PlayerViews (inline in
VideoDetail + fullscreen Player). When the detaching view's player
is nulled on dispose, it holds the last rendered frame instead of
flashing black. The receiving view's surface takeover then renders
the next frame without the ~1-frame black gap. Round-4 audit
HIGH-5 was the closest writeup.
Expected APK-size win from R8: ~30-40%. Need real-device
verification post-install — the keep rules are best-effort and a
missing rule manifests as runtime ClassNotFoundException or
silently-broken kotlinx-serialization decoding.
81 lines
3.6 KiB
Prolog
81 lines
3.6 KiB
Prolog
# SPDX-FileCopyrightText: 2026 Sulkta-Coop
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
#
|
|
# R8 keep rules for the Straw app module. The legacy `app/proguard-rules.pro`
|
|
# is for the upstream NewPipe module — different namespaces, different
|
|
# rules. This file is OURS.
|
|
#
|
|
# AGP's getDefaultProguardFile("proguard-android-optimize.txt") handles
|
|
# the Android framework + AndroidX + Compose runtime defaults via
|
|
# consumer rules shipped with each library. We only need to spell out
|
|
# what those defaults can't see:
|
|
#
|
|
# * UniFFI bindings — reflective FFI dispatch from generated code.
|
|
# * JNA — reflects on every class extending com.sun.jna.Library
|
|
# (that's how the loadLibrary glue works).
|
|
# * Our kotlinx-serialization @Serializable types — their generated
|
|
# $$serializer companions get tree-shaken without explicit keeps.
|
|
# * Media3 session metadata Parcelables.
|
|
|
|
# -- UniFFI -------------------------------------------------------------
|
|
# Generated bindings live under uniffi.strawcore.*. The Rust side calls
|
|
# them via JNI symbol name; if R8 renames the class or methods, every
|
|
# extractor call NPEs.
|
|
-keep class uniffi.strawcore.** { *; }
|
|
-keep class uniffi.** { *; }
|
|
|
|
# -- JNA ---------------------------------------------------------------
|
|
# JNA looks up Library subclasses by Class.forName + reflection at
|
|
# load time. Anything that extends Library or has @FieldOrder must
|
|
# survive.
|
|
-keep class * extends com.sun.jna.Library { *; }
|
|
-keep class com.sun.jna.** { *; }
|
|
-dontwarn com.sun.jna.**
|
|
|
|
# -- kotlinx-serialization ---------------------------------------------
|
|
# Every @Serializable type gets a synthetic Companion + $$serializer
|
|
# class. R8 will strip the $$serializer if nothing visibly calls it
|
|
# (the lookup goes through reflection on the Companion).
|
|
-keepattributes *Annotation*, InnerClasses
|
|
-dontwarn kotlinx.serialization.**
|
|
|
|
-keep,includedescriptorclasses class com.sulkta.straw.**$$serializer { *; }
|
|
-keepclassmembers class com.sulkta.straw.** {
|
|
*** Companion;
|
|
}
|
|
-keepclasseswithmembers class com.sulkta.straw.** {
|
|
kotlinx.serialization.KSerializer serializer(...);
|
|
}
|
|
|
|
# Same dance for our top-level @Serializable types defined outside
|
|
# `com.sulkta.straw.**` (Rust DTOs, etc.). Belt + suspenders.
|
|
-keepclassmembers @kotlinx.serialization.Serializable class * {
|
|
static **$Companion Companion;
|
|
public static <1>$Companion Companion;
|
|
}
|
|
-keepclasseswithmembers @kotlinx.serialization.Serializable class * {
|
|
kotlinx.serialization.KSerializer serializer(...);
|
|
}
|
|
-keep class **$$serializer { *; }
|
|
|
|
# -- Media3 / ExoPlayer ------------------------------------------------
|
|
# Most of Media3 ships consumer rules but session-related Parcelables
|
|
# are reflectively reconstructed across process boundaries (the
|
|
# MediaController talks to PlaybackService via Binder). Keep their
|
|
# field names.
|
|
-keep class androidx.media3.session.** { *; }
|
|
-keep class androidx.media3.common.MediaItem { *; }
|
|
-keep class androidx.media3.common.MediaItem$* { *; }
|
|
-keep class androidx.media3.common.MediaMetadata { *; }
|
|
|
|
# -- Strawcore exceptions / DTOs reflected by UniFFI --------------------
|
|
# StrawcoreError is a sealed Throwable hierarchy exposed via UniFFI.
|
|
# Keep all subclasses + their fields so the Kotlin pattern-match works
|
|
# after minification.
|
|
-keep class com.sulkta.straw.feature.player.** { *; }
|
|
|
|
# -- Reflection-via-Class.forName paths from Compose --------------------
|
|
# Compose's runtime does some Class.forName for its own bootstrap; the
|
|
# AGP consumer rules cover this, but documenting the dependency here
|
|
# so a future bump doesn't surprise us.
|
|
-keep class androidx.compose.runtime.** { *; }
|