diff --git a/.github/workflows/advanced_hardcoded_ci.yml b/.github/workflows/advanced_hardcoded_ci.yml new file mode 100644 index 00000000..6664ee56 --- /dev/null +++ b/.github/workflows/advanced_hardcoded_ci.yml @@ -0,0 +1,98 @@ +name: Advanced Hardcoded CI Build + +on: + workflow_dispatch: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + android-build: + runs-on: ubuntu-latest + env: + JAVA_VERSION: '17' + APK_OUT_DIR: app/build/outputs/apk + ABI32: armeabi-v7a + ABI64: arm64-v8a + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: ${{ env.JAVA_VERSION }} + cache: gradle + + - name: Build debug (signed) and release (unsigned) + shell: bash + run: | + set -euo pipefail + ./gradlew --no-daemon clean assembleDebug assembleRelease + + - name: Validate APK architecture compatibility + shell: bash + run: | + set -euo pipefail + + validate_apk_arch() { + local apk_path="$1" + local label="$2" + + test -n "$apk_path" + if unzip -l "$apk_path" | grep -qE 'lib/[^/]+/'; then + unzip -l "$apk_path" | grep -q "lib/${ABI32}/" + unzip -l "$apk_path" | grep -q "lib/${ABI64}/" + echo "$label contains JNI libs for both ${ABI32} and ${ABI64}." + else + echo "$label has no JNI libs; APK is architecture-agnostic at package level." + fi + } + + DEBUG_APK="$(find "$APK_OUT_DIR/debug" -maxdepth 1 -type f -name 'termux-api-app_*.apk' | head -n1)" + RELEASE_APK="$(find "$APK_OUT_DIR/release" -maxdepth 1 -type f -name 'termux-api-app_*.apk' | head -n1)" + + validate_apk_arch "$DEBUG_APK" "Debug APK" + validate_apk_arch "$RELEASE_APK" "Release APK" + + - name: Generate internal validation keystore + shell: bash + run: | + set -euo pipefail + keytool -genkeypair -v \ + -keystore ci-validation.jks \ + -storepass changeit \ + -keypass changeit \ + -alias ci-validation \ + -keyalg RSA \ + -keysize 2048 \ + -validity 3650 \ + -dname "CN=CI Validation, OU=CI, O=Termux, L=NA, ST=NA, C=US" + + - name: Sign release APK for internal validation + shell: bash + run: | + set -euo pipefail + RELEASE_APK="$(find "$APK_OUT_DIR/release" -maxdepth 1 -type f -name 'termux-api-app_*.apk' | head -n1)" + SIGNED_APK="${RELEASE_APK%.apk}-ci-signed.apk" + "$ANDROID_HOME/build-tools/$(ls "$ANDROID_HOME/build-tools" | sort -V | tail -n1)/apksigner" sign \ + --ks ci-validation.jks \ + --ks-pass pass:changeit \ + --key-pass pass:changeit \ + --ks-key-alias ci-validation \ + --out "$SIGNED_APK" \ + "$RELEASE_APK" + "$ANDROID_HOME/build-tools/$(ls "$ANDROID_HOME/build-tools" | sort -V | tail -n1)/apksigner" verify "$SIGNED_APK" + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: advanced-hardcoded-apks + path: | + app/build/outputs/apk/debug/*.apk + app/build/outputs/apk/release/*.apk + ci-validation.jks diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml new file mode 100644 index 00000000..372aad69 --- /dev/null +++ b/.github/workflows/beta.yml @@ -0,0 +1,100 @@ +name: Beta APK (Android 10 arm32/arm64) + +on: + workflow_dispatch: + inputs: + app_version_name: + description: "SemVer versionName override (ex: 0.53.0-beta.1)" + required: false + default: "0.53.0-beta.1" + +jobs: + build-beta: + runs-on: ubuntu-latest + env: + JAVA_VERSION: "17" + ABI32: armeabi-v7a + ABI64: arm64-v8a + APK_DEBUG_DIR: app/build/outputs/apk/debug + APK_RELEASE_DIR: app/build/outputs/apk/release + TERMUX_API_APP__BUILD__APP_VERSION_NAME: ${{ github.event.inputs.app_version_name }} + TERMUX_API_APP__BUILD__APK_VERSION_TAG: beta-${{ github.run_number }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: ${{ env.JAVA_VERSION }} + cache: gradle + + - name: Build debug and release APK splits + shell: bash + run: | + set -euo pipefail + ./gradlew --no-daemon clean assembleDebug assembleRelease + + - name: Validate ABI split outputs + shell: bash + run: | + set -euo pipefail + + ARM32_DEBUG_APK="$(find "$APK_DEBUG_DIR" -maxdepth 1 -type f -name "*-${ABI32}.apk" | head -n1)" + ARM64_DEBUG_APK="$(find "$APK_DEBUG_DIR" -maxdepth 1 -type f -name "*-${ABI64}.apk" | head -n1)" + ARM32_RELEASE_APK="$(find "$APK_RELEASE_DIR" -maxdepth 1 -type f -name "*-${ABI32}.apk" | head -n1)" + ARM64_RELEASE_APK="$(find "$APK_RELEASE_DIR" -maxdepth 1 -type f -name "*-${ABI64}.apk" | head -n1)" + + test -n "$ARM32_DEBUG_APK" + test -n "$ARM64_DEBUG_APK" + test -n "$ARM32_RELEASE_APK" + test -n "$ARM64_RELEASE_APK" + + echo "arm32 debug: $ARM32_DEBUG_APK" + echo "arm64 debug: $ARM64_DEBUG_APK" + echo "arm32 release: $ARM32_RELEASE_APK" + echo "arm64 release: $ARM64_RELEASE_APK" + + - name: Generate internal beta signing key + shell: bash + run: | + set -euo pipefail + keytool -genkeypair -v \ + -keystore beta-validation.jks \ + -storepass changeit \ + -keypass changeit \ + -alias beta-validation \ + -keyalg RSA \ + -keysize 2048 \ + -validity 3650 \ + -dname "CN=Beta Validation, OU=CI, O=Termux, L=NA, ST=NA, C=US" + + - name: Sign release splits for beta validation + shell: bash + run: | + set -euo pipefail + APKSIGNER="$ANDROID_HOME/build-tools/$(ls "$ANDROID_HOME/build-tools" | sort -V | tail -n1)/apksigner" + for abi in "$ABI32" "$ABI64"; do + UNSIGNED_APK="$(find "$APK_RELEASE_DIR" -maxdepth 1 -type f -name "*-${abi}.apk" | head -n1)" + SIGNED_APK="${UNSIGNED_APK%.apk}-beta-signed.apk" + "$APKSIGNER" sign \ + --ks beta-validation.jks \ + --ks-pass pass:changeit \ + --key-pass pass:changeit \ + --ks-key-alias beta-validation \ + --out "$SIGNED_APK" \ + "$UNSIGNED_APK" + "$APKSIGNER" verify --verbose "$SIGNED_APK" + done + + - name: Upload beta artifacts + uses: actions/upload-artifact@v4 + with: + name: beta-android10-arm32-arm64-apks + path: | + app/build/outputs/apk/debug/*.apk + app/build/outputs/apk/release/*-${{ env.ABI32 }}.apk + app/build/outputs/apk/release/*-${{ env.ABI64 }}.apk + app/build/outputs/apk/release/*-beta-signed.apk + beta-validation.jks diff --git a/.github/workflows/github_action_build.yml b/.github/workflows/github_action_build.yml index 4fa6ad7b..7676a1b0 100644 --- a/.github/workflows/github_action_build.yml +++ b/.github/workflows/github_action_build.yml @@ -40,6 +40,7 @@ jobs: APK_DIR_PATH="./app/build/outputs/apk/debug" APK_VERSION_TAG="$RELEASE_VERSION_NAME.github.debug" # Note the ".", GITHUB_SHA will already have "+" before it APK_BASENAME_PREFIX="termux-api-app_$APK_VERSION_TAG" + EXPECTED_ABIS=("armeabi-v7a" "arm64-v8a") # Used by upload step later echo "APK_DIR_PATH=$APK_DIR_PATH" >> $GITHUB_ENV @@ -53,14 +54,18 @@ jobs: exit_on_error "Build failed for '$RELEASE_VERSION_NAME' release with '$APK_VERSION_TAG' tag." fi - echo "Validating APK file" - if ! test -f "$APK_DIR_PATH/${APK_BASENAME_PREFIX}.apk"; then - files_found="$(ls "$APK_DIR_PATH")" - exit_on_error "Failed to find built APK file at '$APK_DIR_PATH/${APK_BASENAME_PREFIX}.apk'. Files found: "$'\n'"$files_found" - fi + echo "Validating ABI APK files" + for abi in "${EXPECTED_ABIS[@]}"; do + apk_path="$APK_DIR_PATH/${APK_BASENAME_PREFIX}-${abi}.apk" + if ! test -f "$apk_path"; then + files_found="$(ls "$APK_DIR_PATH")" + exit_on_error "Failed to find built ABI APK file at '$apk_path'. Files found: "$' +'"$files_found" + fi + done echo "Generating checksums-sha256.txt file" - if ! (cd "$APK_DIR_PATH"; sha256sum "${APK_BASENAME_PREFIX}.apk" > checksums-sha256.txt); then + if ! (cd "$APK_DIR_PATH"; sha256sum "${APK_BASENAME_PREFIX}-"*.apk > checksums-sha256.txt); then exit_on_error "Generate checksums-sha256.txt file failed for '$RELEASE_VERSION_NAME' release." fi echo "checksums-sha256.txt:"$'\n```\n'"$(cat "$APK_DIR_PATH/checksums-sha256.txt")"$'\n```' @@ -70,6 +75,7 @@ jobs: with: name: ${{ env.APK_BASENAME_PREFIX }} path: | - ${{ env.APK_DIR_PATH }}/${{ env.APK_BASENAME_PREFIX }}.apk + ${{ env.APK_DIR_PATH }}/${{ env.APK_BASENAME_PREFIX }}-armeabi-v7a.apk + ${{ env.APK_DIR_PATH }}/${{ env.APK_BASENAME_PREFIX }}-arm64-v8a.apk ${{ env.APK_DIR_PATH }}/checksums-sha256.txt ${{ env.APK_DIR_PATH }}/output-metadata.json diff --git a/.github/workflows/github_release_build.yml b/.github/workflows/github_release_build.yml index b438db9e..1f8ccb14 100644 --- a/.github/workflows/github_release_build.yml +++ b/.github/workflows/github_release_build.yml @@ -22,42 +22,53 @@ jobs: exit_on_error() { echo "$1" echo "Deleting '$RELEASE_VERSION_NAME' release and '$GITHUB_REF' tag" - hub release delete "$RELEASE_VERSION_NAME" - git push --delete origin "$GITHUB_REF" + gh release delete "$RELEASE_VERSION_NAME" --yes + gh api --method DELETE "repos/${GITHUB_REPOSITORY}/git/${GITHUB_REF}" exit 1 } + + echo "Checking GitHub CLI availability" + if ! gh --version >/dev/null 2>&1; then + exit_on_error "GitHub CLI (gh) is required but not available in runner PATH." + fi + echo "Setting vars" RELEASE_VERSION_NAME="${GITHUB_REF/refs\/tags\//}" if ! printf "%s" "${RELEASE_VERSION_NAME/v/}" | grep -qP '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'; then exit_on_error "The release version '${RELEASE_VERSION_NAME/v/}' is not a valid version as per semantic version '2.0.0' spec in the format 'major.minor.patch(-prerelease)(+buildmetadata)'. https://semver.org/spec/v2.0.0.html." fi - APK_DIR_PATH="./app/build/outputs/apk/debug" - APK_VERSION_TAG="$RELEASE_VERSION_NAME+github.debug" + APK_DIR_PATH="./app/build/outputs/apk/release" + APK_VERSION_TAG="${RELEASE_VERSION_NAME#v}" APK_BASENAME_PREFIX="termux-api-app_$APK_VERSION_TAG" + EXPECTED_ABIS=("armeabi-v7a" "arm64-v8a") echo "Building APK file for '$RELEASE_VERSION_NAME' release with '$APK_VERSION_TAG' tag" export TERMUX_API_APP__BUILD__APK_VERSION_TAG="$APK_VERSION_TAG" # Used by app/build.gradle - if ! ./gradlew assembleDebug; then + if ! ./gradlew assembleRelease; then exit_on_error "Build failed for '$RELEASE_VERSION_NAME' release with '$APK_VERSION_TAG' tag." fi - echo "Validating APK file" - if ! test -f "$APK_DIR_PATH/${APK_BASENAME_PREFIX}.apk"; then - files_found="$(ls "$APK_DIR_PATH")" - exit_on_error "Failed to find built APK file at '$APK_DIR_PATH/${APK_BASENAME_PREFIX}.apk'. Files found: "$'\n'"$files_found" - fi - + echo "Validating ABI APK files" + for abi in "${EXPECTED_ABIS[@]}"; do + apk_path="$APK_DIR_PATH/${APK_BASENAME_PREFIX}-${abi}.apk" + if ! test -f "$apk_path"; then + files_found="$(ls "$APK_DIR_PATH")" + exit_on_error "Failed to find built ABI APK file at '$apk_path'. Files found: "$' +'"$files_found" + fi + done echo "Generating checksums-sha256.txt file" - if ! (cd "$APK_DIR_PATH"; sha256sum "${APK_BASENAME_PREFIX}.apk" > checksums-sha256.txt); then + if ! (cd "$APK_DIR_PATH"; sha256sum "${APK_BASENAME_PREFIX}-"*.apk > checksums-sha256.txt); then exit_on_error "Generate checksums-sha256.txt file failed for '$RELEASE_VERSION_NAME' release." fi echo "checksums-sha256.txt:"$'\n```\n'"$(cat "$APK_DIR_PATH/checksums-sha256.txt")"$'\n```' echo "Uploading files to release" if ! gh release upload "$RELEASE_VERSION_NAME" \ - "$APK_DIR_PATH/${APK_BASENAME_PREFIX}.apk" \ + "$APK_DIR_PATH/${APK_BASENAME_PREFIX}-armeabi-v7a.apk" \ + "$APK_DIR_PATH/${APK_BASENAME_PREFIX}-arm64-v8a.apk" \ "$APK_DIR_PATH/checksums-sha256.txt" \ ; then exit_on_error "Upload files to release failed for '$RELEASE_VERSION_NAME' release." diff --git a/README.md b/README.md index a1cdc520..e4147142 100644 --- a/README.md +++ b/README.md @@ -15,15 +15,66 @@ Latest version is `v0.53.0`. Termux:API application can be obtained from [F-Droid](https://f-droid.org/en/packages/com.termux.api/). -Additionally we provide per-commit debug builds for those who want to try -out the latest features or test their pull request. This build can be obtained -from one of the workflow runs listed on [Github Actions](https://github.com/termux/termux-api/actions/workflows/github_action_build.yml?query=branch%3Amaster+event%3Apush) -page. +Additionally we provide per-commit **debug validation builds** for those who want to try +out the latest features or test their pull request. These artifacts come from +[`github_action_build.yml`](.github/workflows/github_action_build.yml) workflow runs on [GitHub Actions](https://github.com/termux/termux-api/actions/workflows/github_action_build.yml?query=branch%3Amaster+event%3Apush). + +These debug workflow APKs are for validation only and are **not** an official +release substitute for production permission scenarios. Signature keys of all offered builds are different. Before you switch the installation source, you will have to uninstall the Termux application and all currently installed plugins. Check https://github.com/termux/termux-app#Installation for more info. +## Build and release flows + +Use the correct build path depending on your goal: + +- **Validation build (debug / per-commit):** + - Command: `./gradlew assembleDebug` + - Purpose: local verification, CI checks, pull request validation. + - CI workflow: [`github_action_build.yml`](.github/workflows/github_action_build.yml) (builds and uploads debug APK artifacts). + - Output: per-ABI debug APK artifacts (`armeabi-v7a`, `arm64-v8a`) from `app/build/outputs/apk/debug`. + - Important: published debug APK from workflow artifacts is **not** a substitute for the official release APK, especially for production permissions/use-cases. + +- **Official distribution build (signed release):** + - Command: `./gradlew assembleRelease` + - Purpose: official distributable release artifacts only. + - CI workflow: [`github_release_build.yml`](.github/workflows/github_release_build.yml) (builds per-ABI release APKs, checksums, uploads to GitHub Release). + - Requirement: release signing credentials must be provided securely via CI secrets. Do not commit keys, keystore files, or plaintext credentials to the repository. + - Rule: official release must be produced only from the `assembleRelease` path with secure signing configured in CI. + + +## Official ABI support matrix + +Termux:API artifacts are produced only for the ABIs below: + +| ABI | Arch | Validation (debug) | Official release | +| --- | --- | --- | --- | +| `armeabi-v7a` | ARM32 | ✅ | ✅ | +| `arm64-v8a` | ARM64 | ✅ | ✅ | + +Build configuration uses ABI split packaging (`splits { abi { ... } }`) and emits one APK per ABI. +Artifact naming includes ABI suffix: + +- Debug CI: `termux-api-app_.github.debug-.apk` +- Release CI: `termux-api-app_-.apk` + + +## Quick start docs and scripts + +- Build guide: [`docs/BUILD.md`](docs/BUILD.md) +- Permissions matrix: [`docs/PERMISSIONS.md`](docs/PERMISSIONS.md) +- API methods index: [`docs/API_METHODS.md`](docs/API_METHODS.md) +- Troubleshooting: [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md) +- Environment checks: [`scripts/doctor.sh`](scripts/doctor.sh) +- Debug build helper: [`scripts/build-debug.sh`](scripts/build-debug.sh) +- Safe Gradle launcher (Java preflight): [`scripts/gradlew-safe.sh`](scripts/gradlew-safe.sh) +- Developer debugger guide: [`docs/DEVELOPER_DEBUGGER.md`](docs/DEVELOPER_DEBUGGER.md) +- Developer debugger helper: [`scripts/enable-debugger-mode.sh`](scripts/enable-debugger-mode.sh) +- Native lowlevel (C/ASM) notes: [`docs/NATIVE_LOWLEVEL.md`](docs/NATIVE_LOWLEVEL.md) +- Build native helper: [`scripts/build-native-tools.sh`](scripts/build-native-tools.sh) + ## License Released under the [GPLv3 license](http://www.gnu.org/licenses/gpl-3.0.en.html). diff --git a/app/build.gradle b/app/build.gradle index b64dd474..faa0d250 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -43,6 +43,15 @@ android { } } + splits { + abi { + enable true + reset() + include "armeabi-v7a", "arm64-v8a" + universalApk false + } + } + compileOptions { // Flag to enable support for the new language APIs coreLibraryDesugaringEnabled true @@ -53,8 +62,13 @@ android { applicationVariants.all { variant -> variant.outputs.all { output -> - outputFileName = new File("termux-api-app_" + - (apkVersionTag ? apkVersionTag : "v" + versionName + "+" + variant.buildType.name) + ".apk") + def abi = output.getFilter(com.android.build.OutputFile.ABI) + def artifactTag = apkVersionTag ? apkVersionTag : "v" + versionName + "+" + variant.buildType.name + if (abi) { + outputFileName = "termux-api-app_${artifactTag}-${abi}.apk" + } else { + outputFileName = "termux-api-app_${artifactTag}.apk" + } } } diff --git a/docs/API_METHODS.md b/docs/API_METHODS.md new file mode 100644 index 00000000..b6ef0add --- /dev/null +++ b/docs/API_METHODS.md @@ -0,0 +1,69 @@ +# API methods (`api_method`) do Termux:API + +Fonte: `TermuxApiReceiver.java` (switch de `api_method`). + +## Sistema +- BatteryStatus +- Brightness +- Clipboard +- Download +- Fingerprint +- JobScheduler +- Keystore +- Notification +- NotificationChannel +- NotificationList +- NotificationRemove +- NotificationReply +- SAF +- Share +- StorageGet +- Toast +- Usb +- Vibrate +- Volume +- Wallpaper + +## Mídia +- AudioInfo +- CameraInfo +- CameraPhoto +- MediaPlayer +- MediaScanner +- MicRecorder +- SpeechToText +- TextToSpeech +- Torch + +## Sensores +- Sensor +- InfraredFrequencies +- InfraredTransmit + +## Rede +- WifiConnectionInfo +- WifiEnable +- WifiScanInfo + +## Telefonia +- CallLog +- ContactList +- SmsInbox +- SmsSend +- TelephonyCall +- TelephonyCellInfo +- TelephonyDeviceInfo + +## Storage +- SAF +- StorageGet +- MediaScanner + +## UI / interação +- Dialog +- Nfc +- Toast +- Notification +- NotificationChannel +- NotificationRemove +- NotificationReply diff --git a/docs/BUILD.md b/docs/BUILD.md new file mode 100644 index 00000000..c776940a --- /dev/null +++ b/docs/BUILD.md @@ -0,0 +1,61 @@ +# Build do Termux:API + +Este guia reduz fricção para build local sem alterar identidade do app oficial. + +## Requisitos locais + +- Linux/macOS (ou ambiente compatível com shell POSIX). +- JDK 11+ disponível no `PATH`. +- Android SDK instalado (preferencialmente via Android Studio) com platform e build-tools compatíveis com `compileSdk` do projeto. +- Permissão de execução no wrapper: `chmod +x ./gradlew`. + +## Build debug + +```bash +./gradlew assembleDebug +``` + +Uso recomendado: validação local, CI e testes rápidos. + +## Build release + +```bash +./gradlew assembleRelease +``` + +Uso recomendado: fluxo de release. Em produção, exige assinatura segura da trilha oficial. + +## Diferença entre debug e release + +- **debug**: + - assinado com chave de debug/teste; + - usado para validação e desenvolvimento; + - não substitui release oficial para cenários de permissões de produção. +- **release**: + - otimizações/proguard de release; + - destinado a distribuição; + - deve usar assinatura oficial fora de ambiente de teste. + +## Assinatura compatível com Termux oficial + +Para uso real integrado com o ecossistema Termux, o app precisa estar assinado com a **mesma chave** do Termux oficial. Sem isso, integrações protegidas por assinatura (shared UID/permissões signature) não funcionam como no canal oficial. + +## Localização dos APKs + +Após build: + +- Debug: `app/build/outputs/apk/debug/` +- Release: `app/build/outputs/apk/release/` + +O projeto gera APKs por ABI (ex.: `armeabi-v7a` e `arm64-v8a`). + + +## Menor fricção com Java (preflight) + +Se houver múltiplos JDKs no host, use: + +```bash +./scripts/gradlew-safe.sh assembleDebug +``` + +O script tenta usar uma JVM compatível (17..22) via `JAVA17_HOME`, `JAVA21_HOME` ou `JAVA_HOME` antes de chamar `./gradlew`. diff --git a/docs/DEVELOPER_DEBUGGER.md b/docs/DEVELOPER_DEBUGGER.md new file mode 100644 index 00000000..cfd3d309 --- /dev/null +++ b/docs/DEVELOPER_DEBUGGER.md @@ -0,0 +1,50 @@ +# Developer Mode + Debugger App (Termux:API) + +Objetivo: reduzir fricção para usar o Termux:API com ferramentas de depuração do Android **sem mudar identidade/pacote/arquitetura do app**. + +## Pré-condições + +- Opções de desenvolvedor ativas no Android. +- Depuração USB ativa. +- `adb` disponível na máquina host. +- APK debug instalado no dispositivo. + +## Fluxo rápido + +1. Verifique conexão: + +```bash +adb devices +``` + +2. Garanta que o app existe no device: + +```bash +adb shell pm list packages | grep com.termux.api +``` + +3. Ative wait-for-debugger para o processo do app: + +```bash +adb shell am set-debug-app -w com.termux.api +``` + +4. Abra o app e conecte debugger via Android Studio. + +5. Para remover a marcação de debug-app: + +```bash +adb shell am clear-debug-app +``` + +## Seleção manual via UI do Android + +- Configurações → Sistema → Opções do desenvolvedor +- **Selecionar aplicativo de depuração** → `Termux:API` +- (Opcional) habilitar **Aguardar depurador** + +## Observações de segurança e compatibilidade + +- Este fluxo é apenas para desenvolvimento/validação. +- Não altera `applicationId`, `sharedUserId`, `TERMUX_PACKAGE_NAME` nem regras de assinatura oficial. +- Não substitui trilha de release assinada oficialmente. diff --git a/docs/NATIVE_LOWLEVEL.md b/docs/NATIVE_LOWLEVEL.md new file mode 100644 index 00000000..dd763578 --- /dev/null +++ b/docs/NATIVE_LOWLEVEL.md @@ -0,0 +1,26 @@ +# Native lowlevel (C/ASM) utilitário opcional + +Sem alterar comportamento funcional do app Android, este repositório agora inclui um utilitário nativo opcional para validação/diagnóstico local. + +## O que foi adicionado + +- `native/checksum/fast_checksum.c`: rotina de checksum com caminho C puro + trechos inline ASM para ARM32/ARM64. +- `native/checksum/checksum_tool.c`: CLI para calcular checksum de arquivos. +- `scripts/build-native-tools.sh`: compila o binário local (`native/out/checksum_tool`). + +## Build + +```bash +bash scripts/build-native-tools.sh +``` + +## Uso + +```bash +native/out/checksum_tool app/build/outputs/apk/debug/.apk +``` + +## Observações + +- Caminho experimental e opcional, fora da trilha de release oficial Android. +- Não modifica `applicationId`, `TERMUX_PACKAGE_NAME`, `sharedUserId` ou fluxos de assinatura do APK. diff --git a/docs/PERMISSIONS.md b/docs/PERMISSIONS.md new file mode 100644 index 00000000..8b011616 --- /dev/null +++ b/docs/PERMISSIONS.md @@ -0,0 +1,25 @@ +# Permissões do Termux:API + +Tabela prática: API, permissão Android principal, exigência de runtime e risco/limitação. + +| API | Permissão Android necessária | Runtime permission | Risco/limitação | +|---|---|---|---| +| Brightness | `WRITE_SETTINGS` | Especial (tela de settings) | Falha sem liberação manual em “Modificar configurações do sistema”. | +| CameraPhoto | `CAMERA` | Sim | Sem câmera física ou permissão negada => erro. | +| CallLog | `READ_CALL_LOG` | Sim | Dados sensíveis; Android pode restringir em versões recentes. | +| ContactList | `READ_CONTACTS` | Sim | Privacidade; retorno vazio com permissão negada. | +| InfraredFrequencies / InfraredTransmit | `TRANSMIT_IR` | Não (normal) | Só funciona em hardware com IR emissor. | +| Location | `ACCESS_FINE_LOCATION` | Sim | Depende de localização global ligada no Android. | +| MicRecorder / SpeechToText | `RECORD_AUDIO` | Sim | Sem microfone/permissão => indisponível. | +| SmsInbox | `READ_SMS`, `READ_CONTACTS` | Sim | Alto impacto de privacidade; pode ser bloqueado por política OEM. | +| SmsSend | `SEND_SMS`, `READ_PHONE_STATE` | Sim | Pode gerar custo financeiro e bloqueios da operadora. | +| TelephonyCall | `CALL_PHONE` | Sim | Inicia ligação real; depende de suporte de telefonia/SIM. | +| TelephonyCellInfo | `ACCESS_COARSE_LOCATION` | Sim | Pode retornar incompleto sem localização ativa. | +| TelephonyDeviceInfo | `READ_PHONE_STATE` | Sim | Identificadores podem ser mascarados por versão/política Android. | +| WifiScanInfo | `ACCESS_FINE_LOCATION` | Sim | Scans dependem de localização e políticas de scan do Android. | +| NotificationList | Acesso de notificação (`BIND_NOTIFICATION_LISTENER_SERVICE`) | Configuração especial | Requer ativação manual de Notification Listener. | +| Sensor | `BODY_SENSORS` (quando aplicável) | Sim | Sensores variam por dispositivo; pode não haver hardware. | + +## Permissões declaradas no manifest + +O app também declara permissões amplas no `AndroidManifest.xml` para manter compatibilidade funcional com APIs disponíveis (ex.: storage, rede, NFC, áudio, vibração, etc.). Nem toda permissão declarada é requisitada diretamente no switch de `api_method`, mas pode ser usada por componentes específicos da aplicação. diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md new file mode 100644 index 00000000..5bc1c6eb --- /dev/null +++ b/docs/TROUBLESHOOTING.md @@ -0,0 +1,54 @@ +# Troubleshooting do Termux:API + +## 1) App assinado com chave diferente + +**Sintoma:** APIs falham com erro de permissão/integração entre plugins. + +**Causa:** assinatura diferente da usada pelo Termux principal. + +**Ação:** reinstalar conjunto coerente (Termux + plugins) ou usar trilha oficial com mesma assinatura. + +## 2) Termux:API instalado sem pacote `termux-api` + +**Sintoma:** comandos CLI não encontrados ou não disparam chamadas esperadas. + +**Causa:** app Android instalado, mas utilitário de linha de comando ausente no ambiente Termux. + +**Ação:** instalar também o pacote `termux-api` dentro do Termux. + +## 3) Permissão Android negada + +**Sintoma:** API retorna erro/resultado vazio para câmera, áudio, SMS, contatos, etc. + +**Ação:** conceder runtime permissions manualmente em Configurações do Android e repetir o comando. + +## 4) Notification Listener desativado + +**Sintoma:** `NotificationList` sem resultados. + +**Ação:** habilitar acesso de notificação para Termux:API em: +- Configurações Android → Acesso a notificações. + +## 5) `WRITE_SETTINGS` não liberado + +**Sintoma:** API `Brightness` falha. + +**Causa:** `WRITE_SETTINGS` é permissão especial (não é runtime comum). + +**Ação:** liberar em tela específica de “Modificar configurações do sistema” para Termux:API. + +## 6) Localização bloqueada + +**Sintoma:** `Location`, `WifiScanInfo` e parte de `TelephonyCellInfo` sem dados. + +**Ação:** +- conceder permissão de localização; +- habilitar localização global no dispositivo. + +## 7) Problemas de ABI + +**Sintoma:** APK não instala no dispositivo. + +**Causa:** ABI incompatível com a CPU do aparelho. + +**Ação:** usar o APK correto para `armeabi-v7a` (ARM32) ou `arm64-v8a` (ARM64). diff --git a/native/checksum/checksum_tool.c b/native/checksum/checksum_tool.c new file mode 100644 index 00000000..8efba58b --- /dev/null +++ b/native/checksum/checksum_tool.c @@ -0,0 +1,29 @@ +#include +#include +#include + +uint64_t fast_checksum(const uint8_t *data, size_t len); + +int main(int argc, char **argv) { + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + FILE *f = fopen(argv[1], "rb"); + if (!f) { + perror("fopen"); + return 1; + } + + uint8_t buf[8192]; + uint64_t acc = 0; + size_t n; + while ((n = fread(buf, 1, sizeof(buf), f)) > 0) { + acc ^= fast_checksum(buf, n); + } + fclose(f); + + printf("%016llx %s\n", (unsigned long long)acc, argv[1]); + return 0; +} diff --git a/native/checksum/fast_checksum.c b/native/checksum/fast_checksum.c new file mode 100644 index 00000000..17539ef5 --- /dev/null +++ b/native/checksum/fast_checksum.c @@ -0,0 +1,27 @@ +#include +#include + +uint64_t fast_checksum(const uint8_t *data, size_t len) { + uint64_t acc = 0; + size_t i = 0; + +#if defined(__aarch64__) + for (; i + 8 <= len; i += 8) { + uint64_t v; + __asm__ volatile("ldr %0, [%1]" : "=r"(v) : "r"(data + i)); + acc ^= v; + } +#elif defined(__arm__) + for (; i + 4 <= len; i += 4) { + uint32_t v; + __asm__ volatile("ldr %0, [%1]" : "=r"(v) : "r"(data + i)); + acc ^= v; + } +#endif + + for (; i < len; ++i) { + acc = (acc << 5) ^ (acc >> 2) ^ data[i]; + } + + return acc; +} diff --git a/scripts/build-debug.sh b/scripts/build-debug.sh new file mode 100755 index 00000000..7d6ab78e --- /dev/null +++ b/scripts/build-debug.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +set -euo pipefail + +report_dir="build/reports" +report_file="$report_dir/build-debug-report.txt" +mkdir -p "$report_dir" + +{ + echo "Termux:API debug build report" + echo "Generated at: $(date -u +%Y-%m-%dT%H:%M:%SZ)" + echo "Repository: ${GITHUB_REPOSITORY:-local}" + echo "Commit: ${GITHUB_SHA:-unknown}" + echo "Java: $(java -version 2>&1 | head -n1 || true)" + echo + echo "Running: ./scripts/gradlew-safe.sh assembleDebug" +} | tee "$report_file" + +./scripts/gradlew-safe.sh assembleDebug 2>&1 | tee -a "$report_file" + +out_dir="app/build/outputs/apk/debug" +arm32_apk=$(find "$out_dir" -maxdepth 1 -type f -name '*armeabi-v7a*.apk' | head -n1 || true) +arm64_apk=$(find "$out_dir" -maxdepth 1 -type f -name '*arm64-v8a*.apk' | head -n1 || true) + +[[ -n "$arm32_apk" && -f "$arm32_apk" ]] || { echo "Erro: APK armeabi-v7a não encontrado em $out_dir" | tee -a "$report_file"; exit 1; } +[[ -n "$arm64_apk" && -f "$arm64_apk" ]] || { echo "Erro: APK arm64-v8a não encontrado em $out_dir" | tee -a "$report_file"; exit 1; } + +{ + echo + echo "APKs encontrados:" + echo "- $arm32_apk" + echo "- $arm64_apk" + echo + echo "SHA256:" + sha256sum "$arm32_apk" "$arm64_apk" + echo + echo "Report: $report_file" +} | tee -a "$report_file" diff --git a/scripts/build-native-tools.sh b/scripts/build-native-tools.sh new file mode 100755 index 00000000..da78ffd0 --- /dev/null +++ b/scripts/build-native-tools.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail + +cc_bin="${CC:-cc}" +out_dir="native/out" +mkdir -p "$out_dir" + +"$cc_bin" -O3 -std=c11 -Wall -Wextra -o "$out_dir/checksum_tool" \ + native/checksum/checksum_tool.c native/checksum/fast_checksum.c + +echo "Native tool built: $out_dir/checksum_tool" diff --git a/scripts/doctor.sh b/scripts/doctor.sh new file mode 100755 index 00000000..bd603cbc --- /dev/null +++ b/scripts/doctor.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail + +ok() { echo "[OK] $*"; } +warn() { echo "[WARN] $*"; } +err() { echo "[ERR] $*"; exit 1; } + +command -v java >/dev/null 2>&1 && ok "Java detectado: $(java -version 2>&1 | head -n1)" || err "Java não encontrado no PATH" + +[[ -x ./gradlew ]] && ok "Gradle wrapper executável" || err "./gradlew não executável (rode: chmod +x ./gradlew)" + +java_major=$(java -version 2>&1 | awk -F '[\".]' '/version/ {print $2}') +if [[ -z "$java_major" ]]; then + warn "Não foi possível detectar versão principal do Java" +elif [[ "$java_major" -lt 17 || "$java_major" -gt 22 ]]; then + warn "Versão Java atual ($java_major) pode quebrar Gradle/AGP deste projeto. Recomendado: Java 17..22." +else + ok "Faixa Java compatível detectada para Gradle: $java_major" +fi + +if [[ -n "${ANDROID_HOME:-}" && -d "${ANDROID_HOME}" ]]; then + ok "ANDROID_HOME detectado: ${ANDROID_HOME}" +elif [[ -n "${ANDROID_SDK_ROOT:-}" && -d "${ANDROID_SDK_ROOT}" ]]; then + ok "ANDROID_SDK_ROOT detectado: ${ANDROID_SDK_ROOT}" +else + warn "Android SDK não detectado por ANDROID_HOME/ANDROID_SDK_ROOT" +fi + +[[ -f app/build.gradle ]] && ok "app/build.gradle encontrado" || err "app/build.gradle ausente" +[[ -f app/src/main/AndroidManifest.xml ]] && ok "AndroidManifest.xml encontrado" || err "AndroidManifest.xml ausente" + +grep -Eq 'manifestPlaceholders\.TERMUX_PACKAGE_NAME\s*=\s*"com\.termux"' app/build.gradle \ + && ok "TERMUX_PACKAGE_NAME aponta para com.termux" \ + || err "TERMUX_PACKAGE_NAME não aponta para com.termux" + +grep -Eq 'include\s+"armeabi-v7a",\s*"arm64-v8a"' app/build.gradle \ + && ok "ABI splits incluem armeabi-v7a e arm64-v8a" \ + || err "ABI splits não incluem ambas ABIs requeridas" + +ok "Preflight concluído sem dependência externa de ripgrep" diff --git a/scripts/enable-debugger-mode.sh b/scripts/enable-debugger-mode.sh new file mode 100755 index 00000000..83d35fc6 --- /dev/null +++ b/scripts/enable-debugger-mode.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail + +PKG="com.termux.api" + +need() { + command -v "$1" >/dev/null 2>&1 || { echo "Erro: comando '$1' não encontrado"; exit 1; } +} + +need adb + +MODE="${1:-enable}" + +if [[ "$MODE" == "enable" ]]; then + adb get-state >/dev/null 2>&1 || { echo "Erro: nenhum dispositivo adb conectado"; exit 1; } + adb shell pm list packages | grep -q "package:${PKG}" || { echo "Erro: pacote ${PKG} não instalado no dispositivo"; exit 1; } + adb shell am set-debug-app -w "$PKG" + echo "Debugger mode habilitado para ${PKG} (wait-for-debugger)." +elif [[ "$MODE" == "disable" ]]; then + adb shell am clear-debug-app + echo "Debugger mode desabilitado (clear-debug-app)." +else + echo "Uso: $0 [enable|disable]" + exit 1 +fi diff --git a/scripts/gradlew-safe.sh b/scripts/gradlew-safe.sh new file mode 100755 index 00000000..91306c2a --- /dev/null +++ b/scripts/gradlew-safe.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -euo pipefail + +pick_java_home() { + local candidates=() + [[ -n "${JAVA17_HOME:-}" ]] && candidates+=("$JAVA17_HOME") + [[ -n "${JAVA21_HOME:-}" ]] && candidates+=("$JAVA21_HOME") + [[ -n "${JAVA_HOME:-}" ]] && candidates+=("$JAVA_HOME") + + for home in "${candidates[@]}"; do + [[ -x "$home/bin/java" ]] || continue + local major + major=$("$home/bin/java" -version 2>&1 | awk -F '[\".]' '/version/ {print $2}') + if [[ "$major" -ge 17 && "$major" -le 22 ]]; then + echo "$home" + return 0 + fi + done + return 1 +} + +if selected_home="$(pick_java_home)"; then + export JAVA_HOME="$selected_home" + export PATH="$JAVA_HOME/bin:$PATH" + echo "[INFO] Usando JAVA_HOME=$JAVA_HOME" +else + current_major=$(java -version 2>&1 | awk -F '[\".]' '/version/ {print $2}') + echo "[WARN] Nenhum JAVA_HOME compatível (17..22) encontrado. Java atual: $current_major" +fi + +exec ./gradlew "$@" diff --git a/scripts/list-apis.sh b/scripts/list-apis.sh new file mode 100755 index 00000000..c1247aff --- /dev/null +++ b/scripts/list-apis.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail + +receiver="app/src/main/java/com/termux/api/TermuxApiReceiver.java" + +if [[ ! -f "$receiver" ]]; then + echo "Erro: arquivo não encontrado: $receiver" >&2 + exit 1 +fi + +awk -F'"' '/case "[A-Za-z0-9_]+":/ {print $2}' "$receiver" | sort -u