From 427b18cdc5fb7ba6443374dea3b78c26f1f75c95 Mon Sep 17 00:00:00 2001 From: justinzeus Date: Fri, 6 Nov 2026 15:10:52 +0100 Subject: [PATCH 1/3] Add a release-derived optimized TV comparison variant --- app/build.gradle.kts | 11 ++++ app/src/optimized/baseline-prof.txt | 8 +++ app/src/optimized/res/values/strings.xml | 4 ++ docker/README.md | 70 ++++++++++++++++++++++++ 4 files changed, 93 insertions(+) create mode 100644 app/src/optimized/baseline-prof.txt create mode 100644 app/src/optimized/res/values/strings.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 49c0c66d..f0d7554e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -133,6 +133,14 @@ configure { isDebuggable = true applicationIdSuffix = ".debug" } + + create("optimized") { + initWith(getByName("release")) + signingConfig = signingConfigs.getByName("debug") + applicationIdSuffix = ".opt" + versionNameSuffix = "-opt" + matchingFallbacks += "release" + } } flavorDimensions += "version" productFlavors { @@ -199,6 +207,9 @@ configure { kotlin.directories += "$buildDir/generated/seerr_api/src/main/kotlin" kotlin.directories += "src/wolfskin/java" } + getByName("optimized") { + kotlin.directories += "src/release/java" + } } testOptions { diff --git a/app/src/optimized/baseline-prof.txt b/app/src/optimized/baseline-prof.txt new file mode 100644 index 00000000..ad4df0ae --- /dev/null +++ b/app/src/optimized/baseline-prof.txt @@ -0,0 +1,8 @@ +# Hand-maintained startup/shell seed; supplement with device-recorded navigation profiles. +Lcom/github/damontecres/wholphin/WholphinApplication; +HSPLcom/github/damontecres/wholphin/WholphinApplication;->onCreate()V +Lcom/github/damontecres/wholphin/MainActivity; +HSPLcom/github/damontecres/wholphin/MainActivity;->onCreate(Landroid/os/Bundle;)V +Lorg/justinvisser/wolfskin/tv/WolfskinAppKt; +Lorg/justinvisser/wolfskin/tv/WolfskinScreenPreference; +Lorg/justinvisser/wolfskin/tv/ConductarrPreference; diff --git a/app/src/optimized/res/values/strings.xml b/app/src/optimized/res/values/strings.xml new file mode 100644 index 00000000..fa4e585f --- /dev/null +++ b/app/src/optimized/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Wolfskin (Opt) + diff --git a/docker/README.md b/docker/README.md index a40c5a4c..ada3625d 100644 --- a/docker/README.md +++ b/docker/README.md @@ -27,6 +27,76 @@ Upstream enables JVM unit tests only for debug variants; there is no `testDefaultReleaseUnitTest` task. `make test` uses the closest default-flavor task without changing upstream's test configuration. +## Optimized side-by-side comparison + +The `optimized` build type inherits `release`: R8 optimization/minification, +resource shrinking, non-debuggable code, dependency consumer keep rules and +dependency baseline profiles. It changes only signing (the Android debug key), +package (`org.justinvisser.wolfskin.tv.opt`), launcher label (`Wolfskin (Opt)`) +and version-name suffix (`-opt`). The launcher icon/banner are unchanged. +`make apk` remains the clean, release-key-signed production build; neither its +signing flow nor the existing debug build changes. + +Build all four default-flavor outputs in one invocation through the container +wrapper, without a release keystore. On a host whose Gradle cache already holds +the pinned native extensions and their metadata: + +```sh +bash docker/gradle.sh optimized --offline \ + -PWholphinExtensionsUsername=JustinZeus \ + -PWholphinExtensionsPassword=unused \ + :app:assembleDefaultOptimized +``` + +The unused password is not a credential: offline resolution never contacts +GitHub Packages. A cold cache instead needs a read-packages credential supplied +through the existing `ORG_GRADLE_PROJECT_WholphinExtensionsUsername` and +`ORG_GRADLE_PROJECT_WholphinExtensionsPassword` hooks inside the build container, +or securely seeded cached artifacts. No release signing inputs are needed. +Without native extension configuration, the existing dependency selection uses +the MPV stub and omits ffmpeg/AV1, just like `make apk-debug`; that is not a +playback-complete comparison. Inspect native libraries before installing. + +Outputs are in `app/build/outputs/apk/default/optimized/`: universal, arm64-v8a, +armeabi-v7a and x86_64. Keep +`app/build/outputs/mapping/defaultOptimized/mapping.txt` with those APKs to +deobfuscate crashes. This variant shares release library variants and the release +`debugHomeSnapshot` implementation, not `src/debug` or its preview JSON/artwork. +`BuildConfig.DEBUG` remains false: no preview-fixture setting, debug StrictMode +or Compose diagnostic stack traces. + +This is a fresh app sandbox: sign in with Jellyfin Quick Connect or the normal +server/user flow again; it does not inherit the debug/release app's logins or +preferences. **Wolfskin screens** defaults on, and the same Advanced switch can +restore upstream screens. The same Conductarr door +(`https://wolfskin-tv.justinvisser.org`) and URL override apply. The selected +Jellyfin user's token is borrowed for Conductarr; no app-specific TV credential +or embedded secret is added. All other settings retain release behavior, +including update checks; a production release APK is not an update for this +separate package/debug signer. + +`androidx.profileinstaller` and library-provided profiles already exist. There +is no recorded app baseline-profile source or Macrobenchmark module. +`src/optimized/baseline-prof.txt` adds a hand-maintained seed: application and +activity classes plus their `onCreate` methods, `WolfskinAppKt`, +`WolfskinScreenPreference` and `ConductarrPreference`. AGP merges it with library +profiles, rewrites it through R8 and packages `assets/dexopt/baseline.prof` and +`baseline.profm`. It is not a measured navigation profile or evidence of a speedup. +To replace the seed, add an AndroidX Baseline Profile producer/Macrobenchmark +module, record cold launch, Quick Connect/sign-in, person selection, Home, +Libraries, Discover, title pages and playback on a suitable device, then rebuild +and compare startup/frame timings. Device-driven recording and runtime checks +remain separate from an APK build. + +Static verification: use `aapt2 dump badging` for package/label/SDKs and absence +of `application-debuggable`; `apksigner verify --print-certs` for the debug +signer; `unzip -l` for both dexopt assets, native ABI contents and absence of +`assets/wolfskin-*`/`assets/library/` fixtures. Review R8 warnings and effective +consumer rules rather than adding blanket keeps. JVM tests do not prove +minified Hilt/Room/serialization/JNI runtime safety; exercise sign-in, real +Conductarr reads, artwork, database reopen and each player on the comparison box. + + ## Upstream v1.1.0 integration The release merge brings Kotlin `2.4.21`, Gradle `9.8.2`, AGP `9.5.0`, and From 59f4a06815694dad4f93236a70f5d03bb4c0987a Mon Sep 17 00:00:00 2001 From: justinzeus Date: Fri, 6 Nov 2026 15:21:19 +0100 Subject: [PATCH 2/3] Register the optimized baseline profile source explicitly --- app/build.gradle.kts | 1 + app/src/optimized/{ => baselineProfiles}/baseline-prof.txt | 0 docker/README.md | 7 ++++++- 3 files changed, 7 insertions(+), 1 deletion(-) rename app/src/optimized/{ => baselineProfiles}/baseline-prof.txt (100%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f0d7554e..7b4c8e02 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -209,6 +209,7 @@ configure { } getByName("optimized") { kotlin.directories += "src/release/java" + baselineProfiles.directories += "src/optimized/baselineProfiles" } } diff --git a/app/src/optimized/baseline-prof.txt b/app/src/optimized/baselineProfiles/baseline-prof.txt similarity index 100% rename from app/src/optimized/baseline-prof.txt rename to app/src/optimized/baselineProfiles/baseline-prof.txt diff --git a/docker/README.md b/docker/README.md index ada3625d..aa6e8bde 100644 --- a/docker/README.md +++ b/docker/README.md @@ -77,7 +77,7 @@ separate package/debug signer. `androidx.profileinstaller` and library-provided profiles already exist. There is no recorded app baseline-profile source or Macrobenchmark module. -`src/optimized/baseline-prof.txt` adds a hand-maintained seed: application and +`src/optimized/baselineProfiles/baseline-prof.txt` adds a hand-maintained seed: application and activity classes plus their `onCreate` methods, `WolfskinAppKt`, `WolfskinScreenPreference` and `ConductarrPreference`. AGP merges it with library profiles, rewrites it through R8 and packages `assets/dexopt/baseline.prof` and @@ -87,6 +87,8 @@ module, record cold launch, Quick Connect/sign-in, person selection, Home, Libraries, Discover, title pages and playback on a suitable device, then rebuild and compare startup/frame timings. Device-driven recording and runtime checks remain separate from an APK build. +The optimized source set explicitly registers that profile directory; a bare +`src/optimized/baseline-prof.txt` is not consumed by this AGP configuration. Static verification: use `aapt2 dump badging` for package/label/SDKs and absence of `application-debuggable`; `apksigner verify --print-certs` for the debug @@ -95,6 +97,9 @@ signer; `unzip -l` for both dexopt assets, native ABI contents and absence of consumer rules rather than adding blanket keeps. JVM tests do not prove minified Hilt/Room/serialization/JNI runtime safety; exercise sign-in, real Conductarr reads, artwork, database reopen and each player on the comparison box. +`help --task :app:testDefaultOptimizedUnitTest` confirms that this non-debug +variant has no JVM unit-test task, just like release. Build logic is checked +through actual APK assembly and static artifact inspection, not mock unit tests. ## Upstream v1.1.0 integration From 5306f8ddc41ab1fbbc4625fdd8f9c9dece687de3 Mon Sep 17 00:00:00 2001 From: justinzeus Date: Fri, 6 Nov 2026 15:36:20 +0100 Subject: [PATCH 3/3] Keep optimized versions compatible with upgrade and update parsing --- app/build.gradle.kts | 6 +++++- .../damontecres/wholphin/test/VersionCompareTests.kt | 7 +++++++ docker/README.md | 11 +++++++---- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7b4c8e02..98f03a3a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -138,7 +138,6 @@ configure { initWith(getByName("release")) signingConfig = signingConfigs.getByName("debug") applicationIdSuffix = ".opt" - versionNameSuffix = "-opt" matchingFallbacks += "release" } } @@ -240,6 +239,11 @@ androidComponents { "Wholphin-${variant.flavorName}-${variant.buildType}-${output.versionName.get()}-${output.versionCode.get()}$abi.apk" output.outputFileName = outputFileName } + if (variant.name == "defaultOptimized") { + tasks.withType().configureEach { + systemProperty("wolfskin.optimizedVersionName", variant.outputs.first().versionName.get()) + } + } if (variant.name == "defaultDebug") { val packagedApks = variant.artifacts.get(SingleArtifact.APK) val artifactLoader = variant.artifacts.getBuiltArtifactsLoader() diff --git a/app/src/test/java/com/github/damontecres/wholphin/test/VersionCompareTests.kt b/app/src/test/java/com/github/damontecres/wholphin/test/VersionCompareTests.kt index e7e63acd..fdd45e6a 100644 --- a/app/src/test/java/com/github/damontecres/wholphin/test/VersionCompareTests.kt +++ b/app/src/test/java/com/github/damontecres/wholphin/test/VersionCompareTests.kt @@ -1,5 +1,6 @@ package com.github.damontecres.wholphin.test +import com.github.damontecres.wholphin.BuildConfig import com.github.damontecres.wholphin.util.Version import org.junit.Assert import org.junit.Test @@ -18,6 +19,12 @@ class VersionCompareTests { val V_0_1_1_5 = Version.fromString("v0.1.1-5-gabc1234") } + @Test + fun optimizedVersionRemainsCompatibleWithUpgradeAndUpdateChecks() { + val optimizedVersion = Version.fromString(requireNotNull(System.getProperty("wolfskin.optimizedVersionName"))) + Assert.assertEquals(Version.fromString(BuildConfig.VERSION_NAME), optimizedVersion) + } + @Test fun testIsAtLeast() { Assert.assertTrue(V_0_2_0.isAtLeast(V_0_2_0)) diff --git a/docker/README.md b/docker/README.md index aa6e8bde..f014cc90 100644 --- a/docker/README.md +++ b/docker/README.md @@ -32,8 +32,9 @@ task without changing upstream's test configuration. The `optimized` build type inherits `release`: R8 optimization/minification, resource shrinking, non-debuggable code, dependency consumer keep rules and dependency baseline profiles. It changes only signing (the Android debug key), -package (`org.justinvisser.wolfskin.tv.opt`), launcher label (`Wolfskin (Opt)`) -and version-name suffix (`-opt`). The launcher icon/banner are unchanged. +package (`org.justinvisser.wolfskin.tv.opt`) and launcher label (`Wolfskin (Opt)`). +The launcher icon/banner and release-compatible version name are unchanged: +the shared upgrade/updater parser rejects extra suffixes such as `-opt`. `make apk` remains the clean, release-key-signed production build; neither its signing flow nor the existing debug build changes. @@ -98,8 +99,10 @@ consumer rules rather than adding blanket keeps. JVM tests do not prove minified Hilt/Room/serialization/JNI runtime safety; exercise sign-in, real Conductarr reads, artwork, database reopen and each player on the comparison box. `help --task :app:testDefaultOptimizedUnitTest` confirms that this non-debug -variant has no JVM unit-test task, just like release. Build logic is checked -through actual APK assembly and static artifact inspection, not mock unit tests. +variant has no JVM unit-test task, just like release. The existing debug JVM +`VersionCompareTests` receives the actual optimized version name from the variant +API and checks it with the same `Version.fromString` used by upgrade/update handling. +Build logic is otherwise checked through actual APK assembly and static inspection. ## Upstream v1.1.0 integration