From 3cbb71d460a1d72f4f7f8575c26897b4a88f2d12 Mon Sep 17 00:00:00 2001 From: Xavier Roche Date: Sun, 19 Jul 2026 07:56:01 +0200 Subject: [PATCH 1/2] build: target SDK 35 and produce a signed AAB for a closed beta Raises targetSdk 25 -> 35, Play's upload floor. The behaviour changes this crosses are already handled on master: notification channels and POST_NOTIFICATIONS, scoped storage, predictive back. Edge-to-edge, enforced at 35, is opted out via android:windowOptOutEdgeToEdgeEnforcement in values-v35 until the layouts handle window insets (36 removes that opt-out). versionCode bumped to 64. Release builds gain a signingConfig reading a gitignored keystore.properties (sample committed), so bundleRelease produces a signed AAB locally; without the file CI still builds an unsigned release as before. The shipped .so stay 16 KB aligned, verified in the AAB, which 35 makes mandatory. This is the closed-beta vehicle to device-verify Phase 4 and the audit fixes, which have never run on hardware. Not intended for production promotion. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Xavier Roche --- .gitignore | 3 +++ app/build.gradle | 31 +++++++++++++++++++++----- app/src/main/res/values-v35/styles.xml | 7 ++++++ keystore.properties.sample | 6 +++++ 4 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 app/src/main/res/values-v35/styles.xml create mode 100644 keystore.properties.sample diff --git a/.gitignore b/.gitignore index 713afae..0ab1392 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ workspace.xml # per-ABI OpenSSL statics from the toolchain image / fetch-openssl-statics.sh /app/src/main/jni/libiconv-1.17/ /app/src/main/prebuild/ + +# Signing secrets (never commit); see keystore.properties.sample. +/keystore.properties diff --git a/app/build.gradle b/app/build.gradle index df91b42..02ab389 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,21 +12,42 @@ android { defaultConfig { applicationId "com.httrack.android" minSdk 24 - // Left at master's value: 26 would silently drop the channel-less notifications, - // and 29 would take legacy storage away from the still File-based crawl. - targetSdk 25 - versionCode 63 - versionName "3.49.02.63" + // Play's upload floor. The behaviour changes crossed to here are handled: notification + // channels + POST_NOTIFICATIONS (#10), scoped storage (#11), predictive back (#12); + // edge-to-edge (35) is opted out in values-v35 until the layouts handle window insets. + targetSdk 35 + // Must exceed the versionCode currently live on the Play listing before uploading. + versionCode 64 + versionName "3.49.02.64" ndk { abiFilters "arm64-v8a", "x86_64" } } + // Release signing reads a gitignored keystore.properties (see keystore.properties.sample). + // Without it — e.g. on CI — the release stays unsigned, exactly as before. + signingConfigs { + release { + def propsFile = rootProject.file('keystore.properties') + if (propsFile.exists()) { + final Properties props = new Properties() + props.load(new FileInputStream(propsFile)) + storeFile rootProject.file(props.getProperty('storeFile')) + storePassword props.getProperty('storePassword') + keyAlias props.getProperty('keyAlias') + keyPassword props.getProperty('keyPassword') + } + } + } + buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + if (rootProject.file('keystore.properties').exists()) { + signingConfig signingConfigs.release + } } } diff --git a/app/src/main/res/values-v35/styles.xml b/app/src/main/res/values-v35/styles.xml new file mode 100644 index 0000000..980e631 --- /dev/null +++ b/app/src/main/res/values-v35/styles.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/keystore.properties.sample b/keystore.properties.sample new file mode 100644 index 0000000..54cbf6c --- /dev/null +++ b/keystore.properties.sample @@ -0,0 +1,6 @@ +# Copy to keystore.properties (gitignored) to sign a release APK / AAB for a Play upload. +# Use the dedicated UPLOAD keystore (not the 2013 app-signing key). See the signing-key notes. +storeFile=/absolute/path/to/httrack-upload.jks +storePassword=CHANGEME +keyAlias=upload +keyPassword=CHANGEME From 5a76666887675903a124cd84621ef5515fb594ff Mon Sep 17 00:00:00 2001 From: Xavier Roche Date: Sun, 19 Jul 2026 08:08:24 +0200 Subject: [PATCH 2/2] build: fail loudly on an incomplete keystore.properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review follow-up (doubled signing review, sonnet leg, live-probed). A keystore.properties present but missing a key produced a SILENTLY unsigned release AAB — AGP skips signing when the config is not ready — which Play then rejects with a confusing error. The config now throws at once, naming the missing key, so presence-but-incomplete fails at build rather than at upload. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Xavier Roche --- app/build.gradle | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/build.gradle b/app/build.gradle index 02ab389..e84d61a 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -33,6 +33,13 @@ android { if (propsFile.exists()) { final Properties props = new Properties() props.load(new FileInputStream(propsFile)) + // Fail loudly on an incomplete file: a missing key otherwise yields a silently + // unsigned AAB (AGP skips signing when the config is not ready), which Play rejects. + ['storeFile', 'storePassword', 'keyAlias', 'keyPassword'].each { k -> + if (!props.getProperty(k)?.trim()) { + throw new GradleException("keystore.properties is missing '${k}'") + } + } storeFile rootProject.file(props.getProperty('storeFile')) storePassword props.getProperty('storePassword') keyAlias props.getProperty('keyAlias')