# Android CI — JVM unit tests + Android Lint + R8 release-build gate + static # repo scans + secret scan. # # Real Firebase/RC secrets are intentionally ABSENT: the debug build compiles with # an empty APP_CHECK_DEBUG_TOKEN and a placeholder RC_API_KEY (see # app/build.gradle.kts `secret()` resolution). CI-built APKs therefore cannot pass # App Check once enforcement is on — expected. # # The `release-build` job DOES exercise the RELEASE variant (minify+shrink+sign) # using a THROWAWAY keystore + a dummy RC_API_KEY, purely to satisfy the release # guards; that AAB is never distributed. A green release-build proves the R8 # toolchain, not that reflectively-loaded classes survive shrinking at runtime. # # app/google-services.json is gitignored (live Firebase config), but the # google-services Gradle plugin hard-fails without one, so CI copies the dummy # stub from .github/ci/google-services.json before any Gradle task. name: Android CI on: push: branches: [main, dev] pull_request: jobs: unit-tests: runs-on: ubuntu-latest timeout-minutes: 30 steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 with: distribution: temurin java-version: "17" - uses: gradle/actions/setup-gradle@v4 - name: Provide stub google-services.json (real one is gitignored) run: | if [ ! -f app/google-services.json ]; then cp .github/ci/google-services.json app/google-services.json fi - name: Unit tests run: ./gradlew testDebugUnitTest - name: Upload test reports on failure if: failure() uses: actions/upload-artifact@v4 with: name: unit-test-reports path: app/build/reports/tests/ android-lint: runs-on: ubuntu-latest timeout-minutes: 20 steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 with: distribution: temurin java-version: "17" - uses: gradle/actions/setup-gradle@v4 - name: Provide stub google-services.json (real one is gitignored) run: | if [ ! -f app/google-services.json ]; then cp .github/ci/google-services.json app/google-services.json fi # Fails on error-severity issues (NewApi, correctness). This gate caught two # LocalDate.ofInstant NewApi crashes (API 34 call, minSdk 26) on 2026-07-11 # that emulator QA missed because the fixture emulators run API 34+. - name: Android Lint (debug) run: ./gradlew :app:lintDebug - name: Upload lint report on failure if: failure() uses: actions/upload-artifact@v4 with: name: lint-report path: app/build/reports/lint-results-debug.html release-build: runs-on: ubuntu-latest timeout-minutes: 30 steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 with: distribution: temurin java-version: "17" - uses: gradle/actions/setup-gradle@v4 - name: Provide stub google-services.json (real one is gitignored) run: | if [ ! -f app/google-services.json ]; then cp .github/ci/google-services.json app/google-services.json fi # Throwaway upload key — only exists to satisfy the release signing guard. # This AAB is never distributed. - name: Generate throwaway CI signing keystore run: | keytool -genkeypair -v \ -keystore "$RUNNER_TEMP/ci-release.jks" \ -alias ci -keyalg RSA -keysize 2048 -validity 30 \ -storepass ci_ci_ci -keypass ci_ci_ci \ -dname "CN=CI, O=CI, C=US" - name: Build minified release bundle (R8 gate) env: RC_API_KEY: ci_dummy_rc_key_not_real RELEASE_STORE_FILE: ${{ runner.temp }}/ci-release.jks RELEASE_STORE_PASSWORD: ci_ci_ci RELEASE_KEY_ALIAS: ci RELEASE_KEY_PASSWORD: ci_ci_ci # Skip the Crashlytics mapping upload — it needs real Firebase creds the # stub google-services.json does not have. run: ./gradlew :app:bundleRelease -x uploadCrashlyticsMappingFileRelease - name: Upload R8 mapping on failure if: failure() uses: actions/upload-artifact@v4 with: name: r8-mapping path: app/build/outputs/mapping/release/ repo-scans: runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 # Static scanners that gate known regression classes (theme tokens, # navigation wiring, IME flags, painter/XML drawable misuse). - name: Scan scripts run: | bash scripts/theme-scan.sh bash scripts/wiring-scan.sh bash scripts/ime-scan.sh bash scripts/painter-xml-scan.sh gitleaks: runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # full history so the scan covers every commit - name: Run gitleaks env: GITLEAKS_VERSION: "8.21.2" run: | curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" | tar -xz gitleaks ./gitleaks detect --source . --no-banner --redact