Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
4f65bd6
feat(android): register AirPods 5 models and capabilities
cfy114514 Sep 19, 2026
e6a6a98
test(android): share AirPods 4 artwork and add fork-safe debug CI
cfy114514 Sep 19, 2026
a93b5ac
fix(ci): skip unavailable legacy Android SDK tools package
cfy114514 Sep 19, 2026
e19d8db
fix(android): align API 37.0 SDK package and Gradle target
cfy114514 Sep 19, 2026
b80eccd
chore: prepare hash-checked Android compatibility fixes
cfy114514 Sep 20, 2026
c95402a
feat(android): recognize AirPods 5 BLE IDs and fix lint blockers
cfy114514 Sep 20, 2026
4815cf0
fix(android): avoid duplicate SDP registration and unnecessary ATT co…
cfy114514 Sep 20, 2026
8bb2ac8
feat(android): match AirPods 5 controls and artwork to each model
cfy114514 Sep 20, 2026
b48c381
chore(release): prepare AirPods 5 fork release 1.0.0-airpods5.1
cfy114514 Sep 20, 2026
cc22715
chore(release): label the fork root module and remove upstream updates
cfy114514 Sep 20, 2026
f12bc95
fix(android): preserve ATT for Pro 1 custom transparency
cfy114514 Sep 20, 2026
a64485d
fix(android): keep release behavior aligned with debug
cfy114514 Sep 20, 2026
fa2af55
feat(android): add configurable listening mode timer
cfy114514 Sep 20, 2026
c68fb02
ci(android): optionally build signed release APK
cfy114514 Sep 20, 2026
f9fa0fd
fix(android): show sleep timer on main screen
cfy114514 Sep 20, 2026
d5544ba
fix(android): refine sleep timer UI
cfy114514 Sep 21, 2026
560e3a9
Add unit tests for Bluetooth RPA verification, hearing settings encod…
cfy114514 Sep 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
326 changes: 162 additions & 164 deletions .github/workflows/ci-android.yml
Original file line number Diff line number Diff line change
@@ -1,212 +1,210 @@
name: Android CI
name: Android Debug CI

on:
push:
branches:
- '*'
- '**'
paths:
- 'android/**'
- 'root-module-manual/**'
- '.github/workflows/ci-android.yml'
pull_request:
paths:
- 'android/**'
- '.github/workflows/ci-android.yml'
workflow_dispatch:
inputs:
branch:
description: Branch to build
description: Branch or commit to build
required: true
default: main
workflow_call:

# This fork validates debug builds without upstream release keys or publishing.
permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
outputs:
short_sha: ${{ steps.vars.outputs.short_sha }}
app_version: ${{ steps.version.outputs.app_version }}
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
with:
submodules: true
persist-credentials: false
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.branch || github.ref }}

- uses: actions/setup-java@v4
with:
distribution: zulu
java-version: 21

- uses: gradle/actions/setup-gradle@v4
- name: Decode keystore
if: github.event_name != 'pull_request'
run: echo "${{ secrets.RELEASE_KEYSTORE_FILE }}" | base64 --decode > android/release.keystore

- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Accept Licenses
run: yes | sdkmanager --licenses
- name: Install NDK
run: sdkmanager "ndk;30.0.14904198"
- name: Create local.properties
if: github.event_name != 'pull_request'
with:
# Override v3's default: Google no longer serves the legacy "tools" package.
packages: 'platform-tools'
log-accepted-android-sdk-licenses: 'false'

- name: Install project SDK and native build tools
id: sdk
# Keep these versions in sync with android/app/build.gradle.kts.
shell: bash
run: |
cat <<EOF > android/local.properties
RELEASE_STORE_FILE=../release.keystore
RELEASE_STORE_PASSWORD=${{ secrets.RELEASE_STORE_PASSWORD }}
RELEASE_KEY_ALIAS=${{ secrets.RELEASE_KEY_ALIAS }}
RELEASE_KEY_PASSWORD=${{ secrets.RELEASE_KEY_PASSWORD }}
set -euo pipefail
mkdir -p android/build
sdkmanager --list --channel=3 > android/build/ci-sdk-packages.log 2>&1
sdkmanager --channel=3 \
"platforms;android-37.0" \
"ndk;30.0.14904198" \
"cmake;3.22.1" \
2>&1 | tee android/build/ci-sdk-install.log
test -f "$ANDROID_HOME/platforms/android-37.0/android.jar"
test -f "$ANDROID_HOME/ndk/30.0.14904198/source.properties"
test -x "$ANDROID_HOME/cmake/3.22.1/bin/cmake"

- name: Prepare release signing
id: signing
shell: bash
env:
RELEASE_KEYSTORE_BASE64: ${{ secrets.RELEASE_KEYSTORE_BASE64 }}
RELEASE_STORE_PASSWORD: ${{ secrets.RELEASE_STORE_PASSWORD }}
RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }}
RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }}
run: |
set -euo pipefail
if [[ -z "${RELEASE_KEYSTORE_BASE64:-}" || -z "${RELEASE_STORE_PASSWORD:-}" || -z "${RELEASE_KEY_ALIAS:-}" || -z "${RELEASE_KEY_PASSWORD:-}" ]]; then
echo "available=false" >> "$GITHUB_OUTPUT"
echo "Release signing secrets are not configured; skipping signed Release APK."
exit 0
fi
printf '%s' "$RELEASE_KEYSTORE_BASE64" | base64 --decode > "$RUNNER_TEMP/librepods-release.jks"
cat > android/local.properties <<EOF
sdk.dir=$ANDROID_HOME
RELEASE_STORE_FILE=$RUNNER_TEMP/librepods-release.jks
RELEASE_STORE_PASSWORD=$RELEASE_STORE_PASSWORD
RELEASE_KEY_ALIAS=$RELEASE_KEY_ALIAS
RELEASE_KEY_PASSWORD=$RELEASE_KEY_PASSWORD
EOF
- name: Build debug APK for PRs
if: github.event_name == 'pull_request'
run: ./gradlew assembleFossDebug
working-directory: android
- name: Build release artifacts
if: github.event_name != 'pull_request'
run: ./gradlew packageReleaseArtifacts
working-directory: android
- name: Get app version
id: version
run: echo "app_version=$(grep 'appVersionName =' android/app/build.gradle.kts | sed 's/.*= "\(.*\)"/\1/')" >> $GITHUB_OUTPUT
- id: vars
run: echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- uses: actions/upload-artifact@v4
if: github.event_name != 'pull_request'
with:
name: apk-release
path: release/*release.apk
echo "available=true" >> "$GITHUB_OUTPUT"

- uses: actions/upload-artifact@v4
if: github.event_name == 'pull_request'
with:
name: apk-debug
path: android/app/build/outputs/apk/foss/debug/app-foss-debug.apk
- name: Build FOSS debug APK
id: apk
working-directory: android
shell: bash
run: |
set -euo pipefail
chmod +x gradlew
mkdir -p build
./gradlew --no-daemon --stacktrace assembleFossDebug \
2>&1 | tee build/ci-apk.log

- uses: actions/upload-artifact@v4
if: github.event_name != 'pull_request'
with:
name: apk-debug
path: release/*debug.apk
- name: Build signed FOSS release APK
id: release
if: ${{ steps.signing.outputs.available == 'true' }}
working-directory: android
shell: bash
run: |
set -euo pipefail
./gradlew --no-daemon --stacktrace assembleFossRelease \
2>&1 | tee build/ci-release.log

- uses: actions/upload-artifact@v4
if: github.event_name != 'pull_request'
with:
name: root-module-release
path: release/*release.zip
- name: Run JVM unit tests
id: tests
if: ${{ !cancelled() && steps.apk.outcome == 'success' }}
working-directory: android
shell: bash
run: |
set -euo pipefail
./gradlew --no-daemon --stacktrace testFossDebugUnitTest \
2>&1 | tee build/ci-tests.log

- uses: actions/upload-artifact@v4
if: github.event_name != 'pull_request'
with:
name: root-module-debug
path: release/*debug.zip
- name: Run Android Lint
id: lint
if: ${{ !cancelled() && steps.apk.outcome == 'success' }}
working-directory: android
shell: bash
run: |
set -euo pipefail
./gradlew --no-daemon --stacktrace lintFossDebug \
2>&1 | tee build/ci-lint.log

- uses: actions/upload-artifact@v4
if: github.event_name != 'pull_request'
with:
name: release-bundle
path: release/*.aab
- name: Checksum debug APK
if: ${{ !cancelled() }}
shell: bash
run: |
if [[ -f android/app/build/outputs/apk/foss/debug/app-foss-debug.apk ]]; then
cd android/app/build/outputs/apk/foss/debug
sha256sum app-foss-debug.apk > SHA256SUMS
fi

release:
if: github.event_name == 'push'
runs-on: ubuntu-latest
needs: build
permissions:
contents: write
- name: Checksum release APK
if: ${{ !cancelled() && steps.release.outcome == 'success' }}
shell: bash
run: |
cd android/app/build/outputs/apk/foss/release
sha256sum *.apk > SHA256SUMS

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- uses: actions/download-artifact@v4
- name: Upload debug APK
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: apk-release
path: artifacts/apk-release
name: apk-foss-debug-${{ github.run_id }}
path: |
android/app/build/outputs/apk/foss/debug/*.apk
android/app/build/outputs/apk/foss/debug/SHA256SUMS
if-no-files-found: warn
retention-days: 14

- uses: actions/download-artifact@v4
- name: Upload release APK
if: ${{ !cancelled() && steps.release.outcome == 'success' }}
uses: actions/upload-artifact@v4
with:
name: apk-debug
path: artifacts/apk-debug
name: apk-foss-release-${{ github.run_id }}
path: |
android/app/build/outputs/apk/foss/release/*.apk
android/app/build/outputs/apk/foss/release/SHA256SUMS
if-no-files-found: error
retention-days: 14

- uses: actions/download-artifact@v4
- name: Upload test reports and diagnostics
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: root-module-release
path: artifacts/root-module-release
name: android-ci-reports-${{ github.run_id }}
path: |
android/app/build/reports/
android/app/build/test-results/
android/build/ci-*.log
if-no-files-found: warn
retention-days: 14

- uses: actions/download-artifact@v4
with:
name: root-module-debug
path: artifacts/root-module-debug
- id: prev
- name: Write build summary
if: ${{ !cancelled() }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG=$(gh release list \
--limit 1 \
--json tagName \
--jq '.[0].tagName')

echo "tag=$TAG" >> $GITHUB_OUTPUT
- id: changelog
run: |
if [ -z "${{ steps.prev.outputs.tag }}" ]; then
NOTES=$(git log --pretty=format:"- %s ([%h](https://github.com/kavishdevar/librepods/commit/%H))")
else
NOTES=$(git log ${{ steps.prev.outputs.tag }}..HEAD --pretty=format:"- %s ([%h](https://github.com/kavishdevar/librepods/commit/%H))")
fi
echo "notes<<EOF" >> $GITHUB_OUTPUT
echo "$NOTES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT

- id: tag
run: echo "tag=nightly-${{ needs.build.outputs.short_sha }}" >> $GITHUB_OUTPUT

- env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "${{ steps.tag.outputs.tag }}" \
artifacts/**/* \
-t "Nightly ${{ needs.build.outputs.short_sha }}" \
--notes "${{ steps.changelog.outputs.notes }}" \
--prerelease
- name: Get timestamp
id: timestamp
run: echo "timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT
- name: Post to Discord
SDK_OUTCOME: ${{ steps.sdk.outcome }}
APK_OUTCOME: ${{ steps.apk.outcome }}
TEST_OUTCOME: ${{ steps.tests.outcome }}
LINT_OUTCOME: ${{ steps.lint.outcome }}
shell: bash
run: |
curl -X POST "${{ secrets.DISCORD_ANDROID_CI_URL }}?with_components=true" \
-H "Content-Type: application/json" \
-d '{
"embeds": [
{
"title": "LibrePods Nightly Build",
"description": "Download the latest debug and release APKs.",
"color": 253060,
"fields": [
{
"name": "Changelog",
"value": "${{ steps.changelog.outputs.notes }}",
"inline": false
}
],
"timestamp": "${{ steps.timestamp.outputs.timestamp }}",
"footer": {
"text": "GitHub Actions"
}
}
],
"components": [
{
"type": 1,
"components": [
{
"type": 2,
"label": "Download Release APK",
"style": 5,
"url": "https://github.com/kavishdevar/librepods/releases/download/nightly-${{ needs.build.outputs.short_sha }}/LibrePods-FOSS-v${{ needs.build.outputs.app_version }}-release.apk"
},
{
"type": 2,
"label": "Download Debug APK",
"style": 5,
"url": "https://github.com/kavishdevar/librepods/releases/download/nightly-${{ needs.build.outputs.short_sha }}/LibrePods-FOSS-v${{ needs.build.outputs.app_version }}-debug.apk"
}
]
}
]
}'
{
echo '## LibrePods Android debug validation'
echo "SDK installation: ${SDK_OUTCOME}"
echo "APK build: ${APK_OUTCOME}"
echo "Release signing setup: ${SIGNING_OUTCOME}"
echo "Signed Release APK: ${RELEASE_OUTCOME}"
echo "JVM unit tests: ${TEST_OUTCOME}"
echo "Android Lint: ${LINT_OUTCOME}"
echo "Checked-out commit: $(git rev-parse HEAD)"
echo 'All three verification steps are required; no errors are ignored.'
echo 'Artifacts, when produced: debug APK with SHA-256, unit test reports, lint reports, SDK installation logs, and Gradle logs.'
echo 'No release signing keys are used. No release or Discord notification is published.'
echo 'CI does not validate AirPods 5 Bluetooth behavior on real hardware.'
} >> "$GITHUB_STEP_SUMMARY"
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -654,3 +654,18 @@ obj/

# End of https://www.toptal.com/developers/gitignore/api/qt,c++,clion,kotlin,python,android,pycharm,androidstudio,visualstudiocode,linux
linux/.qmlls.ini
phone-main-after-opaque-install.png
phone-main-after-reconnect.png
phone-main-after-relaunch.png
phone-main-after-ui.png
phone-main-before.png
phone-main-bottom-1.png
phone-main-bottom-2.png
phone-main-bottom-after-ui.png
phone-main-bottom-opaque-2.png
phone-main-bottom-opaque.png
phone-main-bottom.png
phone-timer-current.png
phone-timer-opaque.png
docs/performance-audit-2026-09-26.md
release-test-35523018608/SHA256SUMS
Loading