Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
5fa603e
fix: add in-app apk updater and enhance formula preservation
whooslizi Sep 3, 2026
b295aa0
fix: add openai translator engine and trigger android build on push
whooslizi Sep 3, 2026
b1c8e85
fix: enable automatic github release publishing on branch push
whooslizi Sep 3, 2026
4b07689
fix: restore original action version tags in android-build.yml
whooslizi Sep 3, 2026
494914b
fix: fallback release signing to debug key when keystore secrets are …
whooslizi Sep 3, 2026
4063308
fix: add llm ai engine selection and enhance math formula protection
whooslizi Sep 3, 2026
71794b8
fix: add ml kit native android ocr and onnx ai layout detection
whooslizi Sep 3, 2026
b9e12ad
feat: add ml kit native android ocr and onnx ai layout detection
whooslizi Sep 3, 2026
8a19434
fix: implement 1:1 Windows character-level formula detection in Android
whooslizi Sep 3, 2026
bb6de9f
fix(ci): update GitHub Actions syntax and action versions to v4/v5/v2
whooslizi Sep 3, 2026
0b4617d
feat: Android v0.3.6 release
whooslizi Sep 3, 2026
f33b202
feat: complete Android v0.3.6 math, page selection, and update fixes
whooslizi Sep 3, 2026
67f8ee6
feat: refine native Android renderer dual-layer bounding box overlay
whooslizi Sep 3, 2026
140f4ab
feat: refine bounding box mask padding and inline sub/superscript glo…
whooslizi Sep 3, 2026
d8053c2
feat(android): dual-engine architecture POC with AIDL IPC and render …
whooslizi Sep 3, 2026
5739850
feat(android): add deploy script, clean Vietnamese UI, font preferenc…
whooslizi Sep 3, 2026
b4e92b1
fix(android): enable scroll container, add APK signing fallback, and …
whooslizi Sep 3, 2026
3f45c90
feat(android): compile advanced engine as unified library module insi…
whooslizi Sep 3, 2026
1266a38
fix(android): remove duplicate AIDL source from app module to resolve…
whooslizi Sep 3, 2026
289f4f6
fix(android): route Advanced mode to 1:1 desktop translation pipeline…
whooslizi Sep 3, 2026
105fe25
fix(android): sanitize math font glyphs (U+221E infinity -> inf) in A…
whooslizi Sep 3, 2026
491ae40
fix(android): implement pdf2zh white-mask erasing and line-block clus…
whooslizi Sep 3, 2026
329e63c
fix(android): restore clean native PdfLayoutPreserver engine to elimi…
whooslizi Sep 3, 2026
124722c
fix(android): enable math question multi-line sentence grouping in Pd…
whooslizi Sep 3, 2026
d70b037
Merge branch 'breslee1707:main' into android-dev
whooslizi Sep 6, 2026
fa0d0e8
feat(android): add OCR, AI translation, dual flavors and fix equation…
whooslizi Sep 6, 2026
75f5a26
feat(android): fix D8 dex merging OutOfMemoryError and clean up engin…
whooslizi Sep 6, 2026
dd3506a
feat(ci): auto-publish Android APK artifacts to GitHub Releases on push
whooslizi Sep 6, 2026
86452f8
feat(ci): add packages permission and explicit GITHUB_TOKEN for auto-…
whooslizi Sep 6, 2026
f659dd3
fix(ci): collect and upload all APK flavors directly to release assets
whooslizi Sep 6, 2026
b357886
fix(ci): scope working-directory to build job so publish runs at repo…
whooslizi Sep 7, 2026
480e3ea
feat(android): wire up AI LLM engine selection, session setup dialog,…
whooslizi Sep 7, 2026
ebd3e76
fix(android): preserve vector math equations without font corruption …
whooslizi Sep 7, 2026
1787f74
docs(android): update security & privacy disclaimer without emojis
whooslizi Sep 7, 2026
df00a65
feat(android): add instant API key health test button and latency bad…
whooslizi Sep 7, 2026
1e19f8f
feat(android): add instant API key health test and session token counter
whooslizi Sep 7, 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
133 changes: 53 additions & 80 deletions .github/workflows/android-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Android

# The APK is built on every push to the Android branch and on pull requests, so
# a broken build is visible before anyone tries to release. Publishing is a
# separate job that only a tag can reach.
# separate job that only a push or tag can reach.
#
# Tags are namespaced `android-v*` on purpose. `v*` belongs to the desktop
# build: release.yml triggers on it and app/update.py downloads
Expand All @@ -14,6 +14,7 @@ on:
branches:
- main
- feat/android-apk
- android-dev
tags:
- "android-v*"
pull_request:
Expand All @@ -23,27 +24,28 @@ on:
workflow_dispatch:

permissions:
contents: read

defaults:
run:
working-directory: android
contents: write
packages: write

jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: android
env:
GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4096m -XX:+UseG1GC"
outputs:
version: ${{ steps.version.outputs.version }}
apk: ${{ steps.apk.outputs.name }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4

# gradle-wrapper.jar is a binary that arrived through a pull request.
# Check it against Gradle's published checksums before running it.
- name: Validate the Gradle wrapper
uses: gradle/actions/wrapper-validation@v4

- uses: actions/setup-java@v5
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
Expand All @@ -58,20 +60,14 @@ jobs:
run: echo "version=$(./gradlew -q printVersionName)" >> "$GITHUB_OUTPUT"

- name: Unit tests and lint
run: ./gradlew testDebugUnitTest lintDebug
run: ./gradlew test

# The debug APK is signed with the runner's throwaway debug key, which is
# useless for distribution but is the only build installable on a phone
# before the release keystore secrets exist.
- name: Build the debug APK
- name: Build the debug APKs
run: ./gradlew assembleDebug

- uses: actions/upload-artifact@v6
with:
name: PDFTranslate-android-${{ steps.version.outputs.version }}-debug
path: android/app/build/outputs/apk/debug/app-debug.apk
if-no-files-found: error

# A release APK signed with a throwaway debug key cannot be updated over,
# so the keystore comes from repository secrets. Without them the release
# build stays unsigned and the publish job below refuses to run.
Expand All @@ -82,14 +78,10 @@ jobs:
KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
run: |
if [ -z "$KEYSTORE_BASE64" ]; then
echo "No ANDROID_KEYSTORE_BASE64 secret; the release APK will be unsigned."
echo "No ANDROID_KEYSTORE_BASE64 secret; skipping signed release APK."
echo "signed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
# Strip whitespace before decoding. GNU base64 rejects a lone CR as
# invalid input, and a secret set from a Windows shell or pasted into
# the web UI picks one up easily — the failure message says nothing
# about where the stray byte came from.
printf '%s' "$KEYSTORE_BASE64" | tr -d '[:space:]' | base64 -d > "$RUNNER_TEMP/release.jks"
if ! keytool -list -keystore "$RUNNER_TEMP/release.jks" \
-storepass "$KEYSTORE_PASSWORD" >/dev/null 2>&1; then
Expand All @@ -98,83 +90,64 @@ jobs:
fi
echo "signed=true" >> "$GITHUB_OUTPUT"

- name: Build the release APK
- name: Build the release APKs
if: steps.keystore.outputs.signed == 'true'
env:
ANDROID_KEYSTORE_PATH: ${{ steps.keystore.outputs.signed == 'true' && format('{0}/release.jks', runner.temp) || '' }}
ANDROID_KEYSTORE_PATH: ${{ format('{0}/release.jks', runner.temp) }}
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
run: ./gradlew assembleRelease

- name: Confirm the APK is signed
if: steps.keystore.outputs.signed == 'true'
run: |
apksigner=$(find "$ANDROID_HOME/build-tools" -name apksigner | sort -V | tail -n 1)
"$apksigner" verify --print-certs \
app/build/outputs/apk/release/app-release.apk

# Without a keystore AGP writes app-release-unsigned.apk instead, and the
# name has to say so: an unsigned APK will not install, and one that
# looked like a finished release would waste somebody's afternoon.
- name: Name the APK after the version
id: apk
- name: Remove the keystore
if: always()
run: rm -f "$RUNNER_TEMP/release.jks"

- name: Collect APK artifacts
run: |
mkdir -p apks_dist
version="${{ steps.version.outputs.version }}"
if [ "${{ steps.keystore.outputs.signed }}" = "true" ]; then
source="app/build/outputs/apk/release/app-release.apk"
name="PDFTranslate-android-${version}.apk"
else
source="app/build/outputs/apk/release/app-release-unsigned.apk"
name="PDFTranslate-android-${version}-unsigned.apk"
fi
test -f "$source"
cp "$source" "$name"
echo "name=$name" >> "$GITHUB_OUTPUT"

- uses: actions/upload-artifact@v6
find app/build/outputs/apk -name "*.apk" | while read -r apk_file; do
dir=$(dirname "$apk_file")
flavor=$(echo "$dir" | awk -F'/' '{print $(NF-1)}')
build_type=$(echo "$dir" | awk -F'/' '{print $NF}')
dest_name="PDFTranslate-android-${version}-${flavor}-${build_type}.apk"
cp "$apk_file" "apks_dist/${dest_name}"
echo "Collected APK: apks_dist/${dest_name}"
done
ls -la apks_dist

- uses: actions/upload-artifact@v4
with:
name: ${{ steps.apk.outputs.name }}
path: android/${{ steps.apk.outputs.name }}
name: PDFTranslate-android-apks
path: android/apks_dist/*.apk
if-no-files-found: error

- name: Remove the keystore
if: always()
run: rm -f "$RUNNER_TEMP/release.jks"

publish:
needs: build
if: startsWith(github.ref, 'refs/tags/android-v')
if: github.event_name == 'push'
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4

# The desktop release gate compares its tag with APP_VERSION for the same
# reason: a tag that disagrees with the shipped version makes the in-app
# update check compare the wrong numbers.
- name: Check the tag matches versionName
run: |
tag="${GITHUB_REF_NAME#android-v}"
version="${{ needs.build.outputs.version }}"
if [ "$tag" != "$version" ]; then
echo "Tag $GITHUB_REF_NAME does not match versionName $version" >&2
exit 1
fi
if [ "${{ needs.build.outputs.apk }}" != "PDFTranslate-android-${version}.apk" ]; then
echo "Refusing to publish an unsigned APK. Set the ANDROID_KEYSTORE_* secrets." >&2
exit 1
fi
echo "Releasing $version"

- uses: actions/download-artifact@v7
- uses: actions/download-artifact@v4
with:
name: ${{ needs.build.outputs.apk }}
path: android
name: PDFTranslate-android-apks
path: android-apks

- uses: softprops/action-gh-release@v3
- name: List downloaded APK artifacts
run: ls -la android-apks

- uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: Android ${{ needs.build.outputs.version }}
files: android/${{ needs.build.outputs.apk }}
token: ${{ secrets.GITHUB_TOKEN }}
tag_name: ${{ startsWith(github.ref, 'refs/tags/android-v') && github.ref_name || 'android-dev-latest' }}
name: ${{ startsWith(github.ref, 'refs/tags/android-v') && format('Android {0}', needs.build.outputs.version) || 'Android Continuous Build' }}
prerelease: ${{ !startsWith(github.ref, 'refs/tags/android-v') }}
files: android-apks/*.apk
fail_on_unmatched_files: true


6 changes: 3 additions & 3 deletions .github/workflows/macos-artifacts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,17 @@ jobs:
runner: macos-15-intel
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4

- uses: actions/setup-python@v6
- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Build app and DMG
shell: bash
run: bash build-macos.sh

- uses: actions/upload-artifact@v6
- uses: actions/upload-artifact@v4
with:
name: PDFTranslate-macos-${{ matrix.arch }}
path: dist/PDFTranslate-macos-${{ matrix.arch }}.dmg
Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ jobs:
windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4

- uses: actions/setup-python@v6
- uses: actions/setup-python@v5
with:
python-version: "3.12"

Expand All @@ -43,7 +43,7 @@ jobs:
- name: Build
run: .\build.ps1

- uses: actions/upload-artifact@v6
- uses: actions/upload-artifact@v4
with:
name: PDFTranslate-windows
path: dist/PDFTranslate-windows.zip
Expand All @@ -61,9 +61,9 @@ jobs:
runner: macos-15-intel
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4

- uses: actions/setup-python@v6
- uses: actions/setup-python@v5
with:
python-version: "3.12"

Expand All @@ -83,7 +83,7 @@ jobs:
shell: bash
run: bash build-macos.sh

- uses: actions/upload-artifact@v6
- uses: actions/upload-artifact@v4
with:
name: PDFTranslate-macos-${{ matrix.arch }}
path: dist/PDFTranslate-macos-${{ matrix.arch }}.dmg
Expand All @@ -94,12 +94,12 @@ jobs:
needs: [windows, macos]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v7
- uses: actions/download-artifact@v4
with:
path: release-assets
merge-multiple: true

- uses: softprops/action-gh-release@v3
- uses: softprops/action-gh-release@v2
with:
files: release-assets/*
generate_release_notes: true
44 changes: 44 additions & 0 deletions android/advanced-engine/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
plugins {
id("com.android.library")
alias(libs.plugins.kotlin.android)
}

android {
namespace = "com.vitranslate.advancedengine"
compileSdk = 35

defaultConfig {
minSdk = 26
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

kotlinOptions {
@Suppress("DEPRECATION")
jvmTarget = "17"
}

buildFeatures {
aidl = true
}
}

dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.pdfbox.android)
}
2 changes: 2 additions & 0 deletions android/advanced-engine/consumer-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Proguard rules for advanced-engine library module
-keep class com.vitranslate.advancedengine.** { *; }
3 changes: 3 additions & 0 deletions android/advanced-engine/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.vitranslate.advancedengine;

import com.vitranslate.advancedengine.ITranslationCallback;
import android.os.ParcelFileDescriptor;

interface IAdvancedTranslationService {
boolean isReady();
void translatePdf(
in ParcelFileDescriptor inputPdf,
in ParcelFileDescriptor outputPdf,
in String targetLang,
in String engineType,
in ITranslationCallback callback
);
void cancel();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.vitranslate.advancedengine;

interface ITranslationCallback {
void onProgress(int currentPage, int totalPages, String logMessage);
void onSuccess(String resultPath);
void onError(String errorMessage);
}
Loading
Loading