diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 49c0c66d..98f03a3a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -133,6 +133,13 @@ configure { isDebuggable = true applicationIdSuffix = ".debug" } + + create("optimized") { + initWith(getByName("release")) + signingConfig = signingConfigs.getByName("debug") + applicationIdSuffix = ".opt" + matchingFallbacks += "release" + } } flavorDimensions += "version" productFlavors { @@ -199,6 +206,10 @@ configure { kotlin.directories += "$buildDir/generated/seerr_api/src/main/kotlin" kotlin.directories += "src/wolfskin/java" } + getByName("optimized") { + kotlin.directories += "src/release/java" + baselineProfiles.directories += "src/optimized/baselineProfiles" + } } testOptions { @@ -228,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/optimized/baselineProfiles/baseline-prof.txt b/app/src/optimized/baselineProfiles/baseline-prof.txt new file mode 100644 index 00000000..ad4df0ae --- /dev/null +++ b/app/src/optimized/baselineProfiles/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/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 a40c5a4c..f014cc90 100644 --- a/docker/README.md +++ b/docker/README.md @@ -27,6 +27,84 @@ 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`) 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. + +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/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 +`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. +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 +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. +`help --task :app:testDefaultOptimizedUnitTest` confirms that this non-debug +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 The release merge brings Kotlin `2.4.21`, Gradle `9.8.2`, AGP `9.5.0`, and