diff --git a/.github/ISSUE_TEMPLATE/alpha-feedback.yml b/.github/ISSUE_TEMPLATE/alpha-feedback.yml index 3b065e1..face521 100644 --- a/.github/ISSUE_TEMPLATE/alpha-feedback.yml +++ b/.github/ISSUE_TEMPLATE/alpha-feedback.yml @@ -10,7 +10,7 @@ body: id: version attributes: label: Threadline version - description: Use the version shown in Diagnostics, for example 0.1.0-alpha.3 (10003). + description: Copy the version name and version code shown in Diagnostics. validations: required: true - type: input diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index d3932dc..565f713 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -11,23 +11,66 @@ permissions: jobs: verify: runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 45 steps: - - uses: actions/checkout@v6 - - uses: actions/setup-java@v5 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: distribution: temurin java-version: "17" - - uses: android-actions/setup-android@v4 + - uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1 - name: Install Android SDK platform run: sdkmanager "platforms;android-37.0" "build-tools;36.0.0" - - uses: gradle/actions/setup-gradle@v6 + - name: Validate Gradle wrapper + uses: gradle/actions/wrapper-validation@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 + - uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 with: cache-provider: basic - name: Test, lint, and assemble debug and release run: ./gradlew --no-daemon test lint assembleDebug assembleRelease - name: Verify release JNI field names run: ./scripts/verify-release-jni-contract.sh + - name: Install emulator system image + run: sdkmanager "emulator" "system-images;android-35;google_apis;x86_64" + - name: Create emulator + env: + ANDROID_AVD_HOME: ${{ runner.temp }}/threadline-avd + run: | + mkdir -p "${ANDROID_AVD_HOME}" + echo "no" | avdmanager create avd --force --name threadline-ci --package "system-images;android-35;google_apis;x86_64" + "${ANDROID_HOME}/emulator/emulator" -list-avds | grep -Fx threadline-ci + - name: Start emulator + shell: bash + env: + ANDROID_AVD_HOME: ${{ runner.temp }}/threadline-avd + run: | + sudo chmod 666 /dev/kvm + emulator_log="${RUNNER_TEMP}/threadline-emulator.log" + nohup "${ANDROID_HOME}/emulator/emulator" \ + -avd threadline-ci \ + -no-window \ + -noaudio \ + -no-boot-anim \ + -no-metrics \ + -no-snapshot \ + -gpu swiftshader_indirect \ + > "${emulator_log}" 2>&1 & + if ! timeout 300 adb wait-for-device; then + cat "${emulator_log}" + exit 1 + fi + if ! timeout 300 bash -c 'until [[ "$(adb shell getprop sys.boot_completed | tr -d "\r")" == "1" ]]; do sleep 2; done'; then + cat "${emulator_log}" + exit 1 + fi + adb shell settings put global window_animation_scale 0 + adb shell settings put global transition_animation_scale 0 + adb shell settings put global animator_duration_scale 0 + - name: Run instrumented tests + run: ./gradlew --no-daemon connectedDebugAndroidTest + - name: Stop emulator + if: always() + run: adb emu kill || true - name: Package unsigned alpha candidate if: github.event_name == 'push' && github.ref == 'refs/heads/main' id: alpha-candidate @@ -66,7 +109,7 @@ jobs: } >> "${GITHUB_OUTPUT}" - name: Upload unsigned alpha candidate if: github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: ${{ steps.alpha-candidate.outputs.name }} path: ${{ steps.alpha-candidate.outputs.path }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d07586d..98b419e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,6 +15,12 @@ For a change: 5. Do not include passwords, private keys, `.env`, host data, or raw packet logs in commits or bug reports. +Gradle dependency checksums are committed in +`gradle/verification-metadata.xml`. When a dependency or plugin changes, +regenerate the affected entries with Gradle's +`--write-verification-metadata sha256` option and review the resulting checksum +changes before committing them. + SFTP, port forwarding, cloud services, analytics, AI features, and multi-session work remain out of the current MVP unless the user explicitly changes the product scope. Deliberately deferred work belongs in diff --git a/PROJECT_SPEC.md b/PROJECT_SPEC.md index 55eadfe..4fee763 100644 --- a/PROJECT_SPEC.md +++ b/PROJECT_SPEC.md @@ -90,13 +90,15 @@ and it can be reopened from Help. It is not an account setup flow or a multi-pag After the introduction, the user sees saved host profiles and a blank connection form. Only the standard SSH port `22` is prefilled; development-fixture values are never production defaults. -A host contains: +A saved host profile contains: - Display name - Hostname or IP address - Port, default `22` - Username -- Authentication method + +Authentication method is selected for each connection and is not linked to the +saved profile. Credentials are never part of a host profile. Supported authentication for MVP: @@ -1043,7 +1045,7 @@ Exit criterion: > Ten real users can perform small remote tasks for two weeks and provide useful product feedback. -Implementation status (2026-08-03): the accessibility and +Implementation status (2026-08-10): the accessibility and connection/authentication-error pass is implemented, including typed network failures, recovery actions, screen-reader semantics, and 200% font-scale action reachability. Production-path large-output profiling now covers styled Unicode @@ -1078,9 +1080,12 @@ from their decryption secret. Independent restores verified both copies against the established key identity, closing backup recovery. Alpha.3 imported-key authentication and default diagnostic checks subsequently passed on the physical release artifact after force-stop/reopen. Preserving that encrypted -key through the next signed version update and the -limited-versus-public distribution decision remain open. Because the -repository is public, any published GitHub prerelease is public. +key through the next signed version update was then proven when permanent-key +alpha.4 installed over alpha.3 with local state intact and authenticated with +the retained key without re-import. Alpha distribution is direct invited +sharing while Phase 5 is open. Because the repository is public, any published +GitHub prerelease would be public. Sufficient invited technical-alpha use is the +remaining Phase 5 boundary. --- diff --git a/README.md b/README.md index ac526b2..dd931e9 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,26 @@ Threadline is an exploratory, transcript-first SSH client for Android. The product idea is that commands should feel like messages and output should feel like responses, while a real terminal remains underneath for interactive work. -**[Phase 5 — Alpha polish](docs/STATUS.md) is in progress.** Phase 4 security and persistence is -implemented. The app opens on a +## Alpha status + +**[Phase 5: Alpha polish](docs/STATUS.md) is in progress.** Its feature slices +and owner-device acceptance through alpha.4 are complete. The remaining product +boundary is enough invited technical-alpha use to evaluate whether ten real +users can complete small remote tasks for two weeks and provide useful feedback. + +The current accepted signed build is `0.1.0-alpha.4`; the next source candidate +is `0.1.0-alpha.5`. Signed builds are shared directly with invited testers. +There is no public signed APK or GitHub prerelease while Phase 5 is open. +Testers should obtain the APK and its checksum from the owner through the agreed +private channel, then follow the +[technical-alpha guide](docs/alpha-testing.md). +Alpha.5 must pass the signed update and physical-device regression path before +it replaces alpha.4 for testers. + +Threadline has no supported production release. Do not use the alpha for +privileged or sensitive systems. + +Phase 4 security and persistence is implemented. The app opens on a deliberately plain command transcript with a saved multiline composer, streaming command cards, bounded ANSI-aware output, lifecycle status, interactive-terminal suggestions, and one-tap access to the same persistent raw @@ -237,12 +255,14 @@ risks are recorded in ## Project status -Phases 0 through 4 are complete. **Phase 5 — Alpha polish is in progress.** Its +Phases 0 through 4 are complete. **Phase 5: Alpha polish is in progress.** Its accessibility/error, large-output performance, Samsung physical-validation, and basic-onboarding slices are implemented. The installed-update preservation -check is complete. A distribution decision and sufficient technical-alpha use -remain; additional device and OEM coverage, including Pixel, is opportunistic -alpha evidence rather than a separate release gate. +check is complete. Direct invited sharing is the selected alpha distribution +path, with no public prerelease while Phase 5 remains open. Sufficient +technical-alpha use is the remaining boundary. Additional device and OEM +coverage, including Pixel, is opportunistic alpha evidence rather than a +separate release gate. Alpha packaging preparation now defines the permanent release/debug identities, explicit alpha versioning, interactive off-repository key creation, local @@ -261,11 +281,12 @@ release/JNI blocker is closed. Two encrypted off-machine signing-key backups now exist with separately held decryption material, and independent restores verified both copies against the established key identity. Alpha.3 imported-key authentication and default diagnostic checks now pass on the physical release -artifact. Public CI now produces source-bound unsigned candidates while the +artifact. Public CI now produces source-identified unsigned candidates while the permanent signing key remains local. The permanent-key `0.1.0-alpha.4` artifact installed over alpha.3 in place, retained onboarding, profiles, trusted hosts, transcripts, settings, and the encrypted saved key, then authenticated with that -key without re-import. The distribution decision and technical-alpha use remain. +key without re-import. Alpha.5 signed-update acceptance is the next operational +check; invited technical-alpha use remains the product-validation boundary. Use these records according to their purpose: diff --git a/app/src/androidTest/java/dev/threadline/ConnectionFormRetentionTest.kt b/app/src/androidTest/java/dev/threadline/ConnectionFormRetentionTest.kt index 90a1c63..0f576e8 100644 --- a/app/src/androidTest/java/dev/threadline/ConnectionFormRetentionTest.kt +++ b/app/src/androidTest/java/dev/threadline/ConnectionFormRetentionTest.kt @@ -165,8 +165,11 @@ class ConnectionFormRetentionTest { } } - compose.onNodeWithTag(ConnectionFormTags.PRIVATE_KEY_AUTH).performClick() + compose.onNodeWithTag(ConnectionFormTags.PRIVATE_KEY_AUTH) + .performScrollTo() + .performClick() compose.onNodeWithTag(ConnectionFormTags.KEY_PASSPHRASE) + .performScrollTo() .performTextReplacement("session-only") compose.runOnIdle { formVisible.value = false } @@ -315,9 +318,14 @@ class ConnectionFormRetentionTest { } } - compose.onNodeWithTag(ConnectionFormTags.PRIVATE_KEY_AUTH).performClick() - compose.onNodeWithTag(ConnectionFormTags.SAVED_KEY_PREFIX + savedKey.id).performClick() + compose.onNodeWithTag(ConnectionFormTags.PRIVATE_KEY_AUTH) + .performScrollTo() + .performClick() + compose.onNodeWithTag(ConnectionFormTags.SAVED_KEY_PREFIX + savedKey.id) + .performScrollTo() + .performClick() compose.onNodeWithTag(ConnectionFormTags.KEY_PASSPHRASE) + .performScrollTo() .performTextReplacement("session-only") compose.onNodeWithTag(ConnectionFormTags.CONNECT) .performScrollTo() @@ -361,18 +369,27 @@ class ConnectionFormRetentionTest { } } - compose.onNodeWithTag(ConnectionFormTags.PRIVATE_KEY_AUTH).performClick() - compose.onNodeWithTag(ConnectionFormTags.RENAME_KEY_PREFIX + savedKey.id).performClick() + compose.onNodeWithTag(ConnectionFormTags.PRIVATE_KEY_AUTH) + .performScrollTo() + .performClick() + compose.onNodeWithTag(ConnectionFormTags.RENAME_KEY_PREFIX + savedKey.id) + .performScrollTo() + .performClick() compose.onNodeWithTag(ConnectionFormTags.RENAME_KEY_NAME) .performTextReplacement("New name") compose.onNodeWithTag(ConnectionFormTags.CONFIRM_RENAME_KEY).performClick() compose.waitForIdle() assertEquals(savedKey.id to "New name", renamed) - compose.onNodeWithTag(ConnectionFormTags.SAVED_KEY_PREFIX + savedKey.id).performClick() + compose.onNodeWithTag(ConnectionFormTags.SAVED_KEY_PREFIX + savedKey.id) + .performScrollTo() + .performClick() compose.onNodeWithTag(ConnectionFormTags.KEY_PASSPHRASE) + .performScrollTo() .performTextReplacement("delete-me") - compose.onNodeWithTag(ConnectionFormTags.DELETE_KEY_PREFIX + savedKey.id).performClick() + compose.onNodeWithTag(ConnectionFormTags.DELETE_KEY_PREFIX + savedKey.id) + .performScrollTo() + .performClick() assertNull(deletedId) compose.onNodeWithText("Delete saved key?").assertExists() compose.onAllNodesWithText("ssh-ed25519 · SHA256:managed-fixture") @@ -428,15 +445,20 @@ class ConnectionFormRetentionTest { } } - compose.onNodeWithTag(ConnectionFormTags.SAVE_PROFILE).performClick() + compose.onNodeWithTag(ConnectionFormTags.SAVE_PROFILE) + .performScrollTo() + .performClick() compose.waitForIdle() assertEquals("Local fixture", savedProfile?.displayName) compose.onNodeWithTag(ConnectionFormTags.SAVED_PROFILE_PREFIX + original.id) .assertIsSelected() compose.onNodeWithTag(ConnectionFormTags.PASSWORD) + .performScrollTo() .performTextReplacement("session-only") - compose.onNodeWithTag(ConnectionFormTags.SAVED_PROFILE_PREFIX + original.id).performClick() + compose.onNodeWithTag(ConnectionFormTags.SAVED_PROFILE_PREFIX + original.id) + .performScrollTo() + .performClick() compose.onNodeWithTag(ConnectionFormTags.DISPLAY_NAME) .assertEditableTextEquals("Lab") compose.onNodeWithTag(ConnectionFormTags.HOSTNAME) @@ -449,16 +471,22 @@ class ConnectionFormRetentionTest { .assertEditableTextEquals("") compose.onNodeWithTag(ConnectionFormTags.DISPLAY_NAME) + .performScrollTo() .performTextReplacement(" Renamed lab ") - compose.onNodeWithTag(ConnectionFormTags.UPDATE_PROFILE).performClick() + compose.onNodeWithTag(ConnectionFormTags.UPDATE_PROFILE) + .performScrollTo() + .performClick() compose.waitForIdle() assertEquals(original.id, updatedProfile?.first) assertEquals("Renamed lab", updatedProfile?.second?.displayName) assertEquals(HostEndpoint("lab.example", 2200), updatedProfile?.second?.endpoint) compose.onNodeWithTag(ConnectionFormTags.PASSWORD) + .performScrollTo() .performTextReplacement("delete-me") - compose.onNodeWithTag(ConnectionFormTags.DELETE_PROFILE_PREFIX + original.id).performClick() + compose.onNodeWithTag(ConnectionFormTags.DELETE_PROFILE_PREFIX + original.id) + .performScrollTo() + .performClick() assertNull(deletedId) compose.onNodeWithText("Delete saved profile?").assertExists() compose.onAllNodesWithText("operator@lab.example:2200").assertCountEquals(2) @@ -511,6 +539,7 @@ class ConnectionFormRetentionTest { compose.onNodeWithText("To replace this trust record", substring = true).assertExists() compose.onNodeWithTag(ConnectionFormTags.DELETE_TRUST_PREFIX + trustedHost.endpointKey) + .performScrollTo() .performClick() assertNull(deletedEndpointKey) compose.onNodeWithText("Forget trusted server?").assertExists() diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 3066196..2234147 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -174,12 +174,13 @@ product behavior. **Status:** Deferred product design; not required for the current MVP or Phase 5. -The current host profile intentionally stores display name, endpoint, username, -and authentication mode without credentials. Before adding a startup directory, -shell preference, or keep-active preference, define validation, failure behavior, -profile migration, and whether each setting affects only a new connection or an -already active session. Keep-active behavior must also agree with the foreground -service and explicit disconnect contract. +The current host profile intentionally stores display name, endpoint, and +username without an authentication mode or credentials. Before adding an +authentication mode, startup directory, shell preference, or keep-active +preference, define validation, failure behavior, profile migration, and whether +each setting affects only a new connection or an already active session. +Keep-active behavior must also agree with the foreground service and explicit +disconnect contract. ## Generated device keys diff --git a/docs/HISTORY.md b/docs/HISTORY.md index 7cdce02..ce1a8da 100644 --- a/docs/HISTORY.md +++ b/docs/HISTORY.md @@ -69,12 +69,12 @@ AES-256-encrypted archives, and independent downloads, extractions, and key-iden for both provider copies, closing backup recovery. The permanent alpha.3 then authenticated with an encrypted saved fixture key before and after a force-stop/reopen cycle, and its default diagnostic preview matched the documented privacy boundary. -Public CI then began producing source-bound unsigned candidates while permanent signing stayed +Public CI then began producing source-identified unsigned candidates while permanent signing stayed local. The permanent alpha.4 installed over alpha.3 as the same app on the Galaxy S25 Ultra. Its onboarding state, profile, trusted host, transcript history, settings, and encrypted imported key survived, and the retained key authenticated and completed `pwd` without re-import. This closed the -installed-update preservation boundary. The limited-versus-public distribution decision and -technical-alpha use remain. +installed-update preservation boundary. Direct invited sharing was selected for the open Phase 5 +alpha, with no public prerelease until the remaining technical-alpha use boundary is evaluated. Additional device and OEM coverage is opportunistic alpha evidence rather than a separate Pixel gate. See [STATUS.md](STATUS.md) rather than this chronology for the active boundary. diff --git a/docs/STATUS.md b/docs/STATUS.md index dd54de4..9a888d8 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -1,6 +1,6 @@ # Threadline current status -Updated: 2026-08-09 +Updated: 2026-08-10 This is the canonical execution-status page. `PROJECT_SPEC.md` remains the normative product and technical specification. Dated investigations are historical evidence for the boundary they @@ -60,7 +60,7 @@ verification. See the [alpha.1 crash investigation](investigations/2026-08-02-alpha1-release-shrinker-crash.md), and [alpha.2 raw-terminal crash investigation](investigations/2026-08-02-alpha2-raw-terminal-shrinker-crash.md). -Public CI now publishes short-lived unsigned candidates tied to the exact +Public CI now publishes short-lived unsigned candidates labeled with the exact source commit; permanent signing remains local. The permanent-key alpha.4 artifact installed over alpha.3 as the same app on the Galaxy S25 Ultra. The completed onboarding state, saved profile, trusted host, transcript history, @@ -69,8 +69,19 @@ authenticated without re-import and completed `pwd`. This closes the installed update-preservation boundary. See the [alpha.4 update-preservation investigation](investigations/2026-08-09-alpha4-update-preservation.md). +The next source candidate is `0.1.0-alpha.5` (`10005`). Its pre-invite +hardening pins GitHub Actions to immutable commits, validates the Gradle wrapper +and distribution checksum, enforces dependency checksums, verifies 16 KiB APK +alignment after signing, upgrades ConnectBot `sshlib` to 0.4.2, and adds the +core API 35 instrumented suite to CI. Alpha.4 remains the latest accepted signed +artifact until alpha.5 passes the signed update and physical-device regression +path. + ## Remaining Phase 5 boundaries +- Alpha.5 signed-update acceptance on the physical device: retained local + state, password and imported-key authentication, and the structured/raw + same-session path. - Technical-alpha use sufficient to evaluate the Phase 5 exit criterion. ## Alpha distribution: direct invited sharing, decided 2026-08-09 @@ -101,7 +112,7 @@ Two things worth recording so this isn't relitigated from memory: - **The transparency half of the public option is already done.** Checksums, the certificate fingerprint, signature schemes and 16 KiB alignment are published in `investigations/2026-08-09-alpha4-update-preservation.md`, in a public - repository. Only the binary stays private. + repository. Only the signed tester APK stays private. The asymmetry closes it: private can become public later, published can't become unpublished. GitHub will delete a release; anything already mirrored is out. diff --git a/docs/alpha-testing.md b/docs/alpha-testing.md index 1e54a8b..46db53c 100644 --- a/docs/alpha-testing.md +++ b/docs/alpha-testing.md @@ -11,8 +11,9 @@ controlled sequence. ## Before installing -- Obtain the APK from the named public prerelease or directly from the owner - through the agreed private channel. +- Obtain the APK and its checksum directly from the owner through the agreed + private channel. Threadline does not publish a signed tester APK while Phase + 5 is open. - Compare its SHA-256 checksum with the separately published value. - Confirm the release identifies itself as `io.github.r055le.threadline`. - Do not accept a repackaged APK, an unexpected signing-certificate fingerprint, diff --git a/docs/development/release-signing.md b/docs/development/release-signing.md index 58b2eeb..3738b9e 100644 --- a/docs/development/release-signing.md +++ b/docs/development/release-signing.md @@ -154,6 +154,13 @@ public because this repository is public. It contains no signing key or password, cannot update the official app, and is not a distributable Threadline release. +The checksum confirms that a download matches the bytes produced by that CI +run. It is not independent proof that those bytes match the source. The build +workflow reduces that trust gap by pinning every Action to an immutable commit, +validating the Gradle wrapper, checking the wrapper distribution, and enforcing +Gradle dependency checksums. Review changes to those pins and checksums as +supply-chain changes before signing a new candidate. + To sign the exact candidate produced by CI, first identify the successful `Android` run for the intended commit: @@ -209,7 +216,8 @@ The helper then: 1. uses the verified CI candidate, or builds the minified release APK when no candidate is supplied; -2. aligns it before signing; +2. aligns it for 16 KiB pages before signing and verifies that alignment after + signing; 3. signs it with Android SDK `apksigner` using environment-backed password inputs; 4. verifies the APK and prints its public signing-certificate fingerprints; and diff --git a/gradle.properties b/gradle.properties index 20d6d45..59f0103 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,5 +4,5 @@ org.gradle.jvmargs=-Xmx3g -Dfile.encoding=UTF-8 android.useAndroidX=true android.nonTransitiveRClass=true threadline.releaseApplicationId=io.github.r055le.threadline -threadline.versionCode=10004 -threadline.versionName=0.1.0-alpha.4 +threadline.versionCode=10005 +threadline.versionName=0.1.0-alpha.5 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a04658f..1b48502 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ coreKtx = "1.19.0" lifecycle = "2.11.0" coroutines = "1.11.0" material = "1.14.0" -sshlib = "0.4.1" +sshlib = "0.4.2" termlib = "0.1.0" conscrypt = "2.6.1" slf4j = "2.0.18" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml new file mode 100644 index 0000000..0d93206 --- /dev/null +++ b/gradle/verification-metadata.xml @@ -0,0 +1,4415 @@ + + + + true + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a9db115..1f2812c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip +distributionSha256Sum=9c0f7faeeb306cb14e4279a3e084ca6b596894089a0638e68a07c945a32c9e14 networkTimeout=10000 retries=0 retryBackOffMs=500 diff --git a/scripts/build-signed-alpha.sh b/scripts/build-signed-alpha.sh index 47e5de8..9af24af 100755 --- a/scripts/build-signed-alpha.sh +++ b/scripts/build-signed-alpha.sh @@ -134,7 +134,7 @@ if [[ -z ${THREADLINE_RELEASE_KEY_PASSWORD:-} ]]; then fi export THREADLINE_RELEASE_STORE_PASSWORD THREADLINE_RELEASE_KEY_PASSWORD trap 'unset THREADLINE_RELEASE_STORE_PASSWORD THREADLINE_RELEASE_KEY_PASSWORD; rm -f "$aligned_apk"' EXIT -"$zipalign_command" -f -p 4 "$unsigned_apk" "$aligned_apk" +"$zipalign_command" -P 16 -f 4 "$unsigned_apk" "$aligned_apk" "$apksigner_command" sign \ --ks "$THREADLINE_RELEASE_STORE_FILE" \ --ks-key-alias "$THREADLINE_RELEASE_KEY_ALIAS" \ @@ -142,6 +142,7 @@ trap 'unset THREADLINE_RELEASE_STORE_PASSWORD THREADLINE_RELEASE_KEY_PASSWORD; r --key-pass env:THREADLINE_RELEASE_KEY_PASSWORD \ --out "$output_apk" \ "$aligned_apk" +"$zipalign_command" -c -P 16 4 "$output_apk" "$apksigner_command" verify --verbose --print-certs "$output_apk" (