Skip to content

Comments

Migrate from python-for-android to Chaquopy#259

Open
rtibbles wants to merge 7 commits intodevelopfrom
chaquopy_refactor
Open

Migrate from python-for-android to Chaquopy#259
rtibbles wants to merge 7 commits intodevelopfrom
chaquopy_refactor

Conversation

@rtibbles
Copy link
Member

@rtibbles rtibbles commented Jan 17, 2026

Summary

Migrates the Kolibri Android app from python-for-android (p4a) to Chaquopy for Python integration. This is a major architectural change that:

  • Replaces p4a with Chaquopy: Uses Chaquopy Gradle plugin for Python 3.10 integration instead of the p4a build system
  • Moves to standard Android project structure: Reorganizes from python-for-android/dists/kolibri/ to standard app/ module
  • Introduces WebView-based UI: Replaces p4a's PythonActivity with a native WebViewActivity that loads Kolibri via HTTP server
  • Simplifies the build process: Uses standard Gradle commands instead of custom p4a toolchain
  • Preserves task worker architecture: Maintains WorkManager-based background task execution for content downloads, sync operations, etc.
  • Drops x86 (32-bit) architecture: Now builds for armeabi-v7a, arm64-v8a, and x86_64 (Chromebooks/emulators)
  • Reduces APK size significantly: Universal APK reduced from ~200MB to ~74MB
  • Adds animated splash screen: Vector drawable Kolibri logo with wing-flap animation during server startup
  • Adds unit tests: JUnit + Robolectric tests for utility classes and task workers
  • Adds development tooling: CDP helper for WebView DOM inspection, Maestro smoke test, agent guides
  • Updates README with new dev setup: Complete documentation for the new build process

Commit history

  1. Remove obsolete python-for-android infrastructure — deletes p4a dist, kivy/renpy/jtar runtime, old Python source, Docker, Buildkite
  2. Add Gradle project with Chaquopy-based Android app — all new Java/Python source, Android resources, build configuration
  3. Update CI workflows and build scripts for Chaquopy — GitHub Actions, Makefile, pre-commit, build scripts
  4. Add unit tests for utility classes and task workers — AuthUtils, ContextUtil, ShareUtils, BaseTaskWorker
  5. Update README for Chaquopy-based architecture — full documentation rewrite
  6. Add development and testing tooling — AGENTS.md, CDP helper, Maestro smoke test

Manual verification performed

  1. Built debug APK successfully with ./gradlew assembleDebug
  2. Installed on Android emulator
  3. Verified Kolibri server starts correctly (confirmed via logcat)
  4. Verified WebView loads Kolibri UI and displays channels
  5. Verified navigation within the app works (channel browsing)
  6. Verified animated splash screen displays during startup
  7. Ran unit tests (./gradlew test) — all pass

Screenshots

Learn page with channels after fresh app start: Channel content view showing navigation:
Kolibri Learn Page Kolibri Channel View

References

Reviewer guidance

How to test

Follow the Development Setup section in the updated README:

  1. Ensure JDK 17+ is installed
  2. Run make setup to download Android SDK, system image, and create the emulator AVD
  3. Set up Python virtual environment: python3 -m venv venv && source venv/bin/activate && pip install -r build-requirements.txt
  4. Place a Kolibri tar file in the tar/ directory
  5. Run make emulator to start the emulator
  6. Run make install to build and install the APK
  7. Verify the app launches and displays content

CI validation

The build_apk workflow should produce a valid APK artifact — please verify the CI check completes successfully. The build_and_test workflow runs unit tests on every push.

Risky areas requiring careful review

  • app/build.gradle:150-180 — Custom Gradle task for patching Kolibri tar archive (extracts, patches settings, repackages)
  • KolibriEnvironmentSetup.java — Python environment initialization sequence and plugin enabling
  • KolibriServerService.java — Background service lifecycle for HTTP server
  • WebViewActivity.java — WebView configuration, auth token flow, splash screen lifecycle, and navigation handling
  • task_reconciler.py — Python-side task reconciliation between Kolibri and WorkManager
  • main.py — Kolibri server startup sequence and lifecycle management

Key architectural changes

Component Before (p4a) After (Chaquopy)
Python integration p4a bootstrap, custom recipes Chaquopy Gradle plugin
UI PythonActivity (SDL-based) WebViewActivity + Service Worker
Splash screen Static presplash.jpg Animated vector drawable (wing flap)
HTTP server Started from Python main Started via KolibriServerService
Task workers Direct Python process WorkManager + Chaquopy Python
Build system buildozer/p4a commands Standard Gradle
CI Buildkite + Docker GitHub Actions
Architectures armeabi-v7a, arm64-v8a, x86, x86_64 armeabi-v7a, arm64-v8a, x86_64
Universal APK size ~200MB ~74MB
APK build time 25 minutes 2 minutes
Unit tests None JUnit + Robolectric (4 test classes)

🤖 Generated with Claude Code

@rtibbles rtibbles force-pushed the chaquopy_refactor branch 5 times, most recently from 9007d2d to 4eb9820 Compare January 17, 2026 22:24
@rtibbles rtibbles requested a review from rtibblesbot February 8, 2026 18:37
Copy link

@rtibblesbot rtibblesbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this major migration! Moving from python-for-android to Chaquopy is a great step forward - the reduced APK size (~200MB to ~74MB) and faster build times (25min to 2min) are impressive wins. The new WebView-based architecture is clean and the code is well-organized.

I found no blocking issues - CI is green and the manual verification screenshots look solid. I have several suggestions and nitpicks that could improve robustness, particularly around edge-case error handling and thread safety.

Summary of findings:

  • 0 blocking issues
  • 8 suggestions (resource safety, null guards, thread safety, design clarity)
  • 3 nitpicks (logging, unused code, minor style)

Nice work on the migration! The architecture is significantly cleaner than the p4a bootstrap approach.

Copy link

@rtibblesbot rtibblesbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this ambitious migration from python-for-android to Chaquopy! This is clearly a significant improvement -- the APK size reduction, build time improvement, and architectural cleanup are all excellent outcomes. The PR is well-structured with a thorough description and manual verification steps.

I found several issues that should be addressed before merging, organized by severity:

Blocking issues (5):

  1. CI failure: patchKolibriTar task fails when no tar file is present, breaking unit tests
  2. WebView security: MIXED_CONTENT_ALWAYS_ALLOW is unnecessarily permissive
  3. Thread safety: KolibriServerService.startHttpServer() has a race window for duplicate thread creation
  4. Supply chain risk: fkirc/skip-duplicate-actions@master is unpinned in CI
  5. Task reconciler: Tag filtering heuristic for distinguishing job IDs from class names is fragile

Key suggestions (not blocking but strongly recommended):

  • WebView setAllowFileAccess(true) and third-party cookies are unnecessary security relaxations
  • KolibriEnvironmentSetup.initialized should be volatile for thread-safe reads
  • Server state should be reset in KolibriServerService.onDestroy()
  • Duplicated tar extraction logic between configuration-phase and task should be consolidated

The inline comments below have the full details. Looking forward to the updates!

@rtibbles rtibbles force-pushed the chaquopy_refactor branch 4 times, most recently from 4d6a145 to 359de57 Compare February 9, 2026 23:23
Copy link

@rtibblesbot rtibblesbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Impressive migration from python-for-android to Chaquopy. The APK size reduction, build time improvement, and move to standard Android project structure are significant wins.

CI passing (all 7 checks green). Screenshots confirm the Learn page and channel navigation work correctly.

Blocking (not addressable as inline comments):

  • app/src/main/AndroidManifest.xml:92android:permission="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE, android.permission.FOREGROUND_SERVICE_DATA_SYNC" is invalid. android:permission accepts only a single permission string. This comma-separated value is treated as one literal string, which will never match. Either remove this attribute (the foregroundServiceType declaration + <uses-permission> should suffice) or split into separate service declarations.
  • .github/workflows/build_apk.yml:190cat .version-code reads a file that the new Chaquopy-based build never generates. This step will fail or produce empty output, breaking the release pipeline. Either write this file during the build or extract the version code from the APK via aapt2 dump badging.
  • app/src/main/AndroidManifest.xml:17 — Stale comment says <!-- Allow writing to external storage --> but the permission on line 18 is android.permission.INTERNET.

See inline comments for remaining blocking issues and suggestions.

@rtibbles
Copy link
Member Author

Thanks for the review, taking a look now.

Copy link

@rtibblesbot rtibblesbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Significant progress since last review — the vast majority of prior findings have been addressed.

CI passing (all 7 checks green: linting, build, unit tests, smoke test). PR targets develop (the default branch).

21 prior finding(s) resolved. The WebView security hardening, thread safety fixes, supply chain pinning, task reconciler prefix tags, try-with-resources, volatile fields, and other feedback have all been incorporated cleanly.

Unresolved prior findings:

  • [suggestion] AndroidManifest.xml:92 — comma-separated android:permission is invalid — UNRESOLVED. android:permission accepts a single permission string, but line 92 has "android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE, android.permission.FOREGROUND_SERVICE_DATA_SYNC" which is one literal string containing a comma. This doesn't break the app (the attribute only restricts external callers, and this is an internal AndroidX service), but it's misleading — either remove the attribute entirely (the foregroundServiceType + <uses-permission> declarations are sufficient) or split into separate <service> declarations.

  • [suggestion] .github/workflows/build_apk.yml:190cat .version-code reads a file that the new build never generates — UNRESOLVED. The Chaquopy/Gradle build computes versionCode at configuration time in app/build.gradle but never writes a .version-code file. This step currently produces an empty version-code output. It doesn't break CI (the output isn't consumed by build_and_test.yml), but it will break the release pipeline when release_apk.yml tries to use this value. Either write the file during the build (e.g., a doLast in the Gradle build) or extract the version code from the APK via aapt2 dump badging.

  • [nitpick] AndroidManifest.xml:17 — stale comment — UNRESOLVED. Comment says <!-- Allow writing to external storage --> but the permission on line 18 is android.permission.INTERNET.


@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Compared the current PR state against findings from a prior review:

  • Retrieved prior bot reviews deterministically via the GitHub API
  • Classified each prior finding as RESOLVED, UNRESOLVED, PARTIALLY_ADDRESSED, or CONTESTED
  • Only raised NEW findings for newly introduced code
  • Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
  • Checked CI status and linked issue acceptance criteria

@rtibbles
Copy link
Member Author

Revision Summary

Blocking items fixed

  • Added missing super.onCreate() call in WorkControllerService (b30c60e)
  • Quoted shell interpolation in CI workflow to prevent injection (b30c60e)

Suggestions addressed

  • Fixed WebView field shadowing in WebViewActivity (b30c60e)
  • Replaced broad usesCleartextTraffic="true" with scoped network security config allowing cleartext only to 127.0.0.1 and localhost (b30c60e)
  • Fixed taskCount leak — increment moved into startTask() so decrement always has a matching increment (b30c60e)
  • Fixed misleading "stopping service" log in onTrimMemory (b30c60e)
  • Added custom WebViewClient to open external URLs in system browser, keep local URLs in WebView (b30c60e)
  • Moved Kolibri init off main thread with CompletableFuture to avoid ANR — App.onCreate() kicks off async, server thread and work controller await completion (b74d75e)
  • Fixed server restart crash caused by stale _queue_logging_initialized_for_process flag in Kolibri's logger (b74d75e)
  • Added WebView history clearing after first real page load so back button doesn't return to loading screen (b74d75e)

Declined (with reasoning)

  • Foreground service: Deliberate design — previous implementation's persistent server drained battery. Android expectation is that backgrounding stops resource consumption. Can revisit in follow-up if needed.
  • Default WebViewClient: Addressed by implementing a custom one that handles external links.

@rtibbles
Copy link
Member Author

Addressed the three items from the review body in 0b8e49e:

  1. AndroidManifest.xml:92 — invalid android:permission: Removed the comma-separated android:permission attribute. The <uses-permission> declarations + foregroundServiceType are sufficient.

  2. build_apk.yml:190 — stale .version-code: Added a line in build.gradle to write .version-code to the project root during the build, so the CI step can read it.

  3. AndroidManifest.xml:17 — stale comment: Removed the "Allow writing to external storage" comment above the INTERNET permission.

Copy link

@rtibblesbot rtibblesbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All prior findings addressed. The latest commit (0b8e49e) fixes the invalid comma-separated android:permission, removes the stale manifest comment, and writes .version-code for CI.

CI: linting, build, unit tests all passing. Smoke test in progress.

25+ prior finding(s) resolved.


@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Compared the current PR state against findings from a prior review:

  • Retrieved prior bot reviews deterministically via the GitHub API
  • Classified each prior finding as RESOLVED, UNRESOLVED, PARTIALLY_ADDRESSED, or CONTESTED
  • Only raised NEW findings for newly introduced code
  • Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
  • Checked CI status and linked issue acceptance criteria

Copy link

@rtibblesbot rtibblesbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All prior findings from previous reviews have been resolved — nice work addressing everything systematically.

CI passing (Linting green). One new suggestion below.


@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Compared the current PR state against findings from a prior review:

  • Retrieved prior bot reviews deterministically via the GitHub API
  • Classified each prior finding as RESOLVED, UNRESOLVED, PARTIALLY_ADDRESSED, or CONTESTED
  • Only raised NEW findings for newly introduced code
  • Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
  • Checked CI status and linked issue acceptance criteria

Copy link

@rtibblesbot rtibblesbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All prior blocking issues, suggestions, and nitpicks from previous reviews have been addressed. Nice work on the thorough revisions.

23 prior finding(s) resolved.

CI passing (Linting green). Two new minor findings below.

  • [suggestion] Lambda parameter shadows field in WorkController.dispatch() — see inline comment
  • [nitpick] WorkControllerService.onDestroy() missing super.onDestroy() — see inline comment

@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Compared the current PR state against findings from a prior review:

  • Retrieved prior bot reviews deterministically via the GitHub API
  • Classified each prior finding as RESOLVED, UNRESOLVED, PARTIALLY_ADDRESSED, or CONTESTED
  • Only raised NEW findings for newly introduced code
  • Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
  • Checked CI status and linked issue acceptance criteria

Copy link

@rtibblesbot rtibblesbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All prior findings from three rounds of review have been thoroughly addressed. The fixes are solid — highlights include the synchronized startHttpServer() eliminating the race window, MIXED_CONTENT_NEVER_ALLOW + disabled file/content access hardening the WebView, the kolibri:job: tag prefix making reconciliation robust, scoped network security config replacing blanket cleartext, and the CompletableFuture-based async init preventing ANR.

CI passing (7/7 checks green). Code is clean and well-structured.

16 prior finding(s) resolved.


@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Compared the current PR state against findings from a prior review:

  • Retrieved prior bot reviews deterministically via the GitHub API
  • Classified each prior finding as RESOLVED, UNRESOLVED, PARTIALLY_ADDRESSED, or CONTESTED
  • Only raised NEW findings for newly introduced code
  • Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
  • Checked CI status and linked issue acceptance criteria

Remove the entire python-for-android build system, kivy/renpy runtime,
vendored jtar/jnius libraries, old Python source, Docker build,
Buildkite CI, and related configuration files. This clears the way
for the Chaquopy-based architecture.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rtibbles rtibbles force-pushed the chaquopy_refactor branch 2 times, most recently from ed75ba4 to 382f479 Compare February 21, 2026 02:57
Copy link

@rtibblesbot rtibblesbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All prior blocking issues, suggestions, and nitpicks from the three previous reviews have been addressed. The WebView security hardening (MIXED_CONTENT_NEVER_ALLOW, file access disabled, scoped network security config, custom WebViewClient), thread safety fixes (synchronized startHttpServer, volatile fields, CompletableFuture-based async init), CI fixes (pinned actions, .version-code generation), manifest corrections, and task reconciler improvements are all solid.

CI: Linting, build, and unit tests passing. Smoke test pending.

~25 prior finding(s) resolved.

Prior findings still open:

  • [suggestion] Unquoted ${{ }} in CI workflow run: steps — PARTIALLY_ADDRESSED. build_and_test.yml was properly quoted (tar="${{ ... }}"), but build_apk.yml lines 134 and 137 still use unquoted ${{ github.event.inputs.tar-url }} / ${{ inputs.tar-url }}. Low risk since workflow_dispatch requires write access, but quoting or using an env var is best practice per GitHub's security hardening guide.

New findings:

  • See inline comment on onTrimMemory.

@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Compared the current PR state against findings from a prior review:

  • Retrieved prior bot reviews deterministically via the GitHub API
  • Classified each prior finding as RESOLVED, UNRESOLVED, PARTIALLY_ADDRESSED, or CONTESTED
  • Only raised NEW findings for newly introduced code
  • Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
  • Checked CI status and linked issue acceptance criteria

Copy link

@rtibblesbot rtibblesbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All prior findings have been thoroughly addressed across the revision commits. Nice work on the fixes.

16 prior finding(s) resolved. All blocking issues (manifest permissions, CI .version-code, WebView security, thread safety, supply chain pinning, task reconciler heuristic) and suggestions (volatile fields, server state reset, cleartext scoping, field shadowing, external URL handling, async init) are confirmed fixed in the current code.

CI: 6/7 checks green (lint, build, unit tests, download tar, path checks). Smoke test still in progress — worth confirming before merge.

One new nitpick below.


@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly

How was this generated?

Compared the current PR state against findings from a prior review:

  • Retrieved prior bot reviews deterministically via the GitHub API
  • Classified each prior finding as RESOLVED, UNRESOLVED, PARTIALLY_ADDRESSED, or CONTESTED
  • Only raised NEW findings for newly introduced code
  • Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
  • Checked CI status and linked issue acceptance criteria

rtibbles and others added 2 commits February 23, 2026 07:19
Replace the python-for-android/kivy build system with a standard Gradle
project using Chaquopy for Python integration. This is the core of the
architecture migration.

Build system:
- Gradle wrapper (8.11.1), root and app build files
- Chaquopy plugin for embedding Python in the Android app
- Updated .gitignore, requirements.txt, pyproject.toml

Java application layer:
- App, WebViewActivity, KolibriWebChromeClient for WebView-based UI
- KolibriServerService/ViewModel for managing the Kolibri HTTP server
- KolibriEnvironmentSetup for Python environment configuration
- WorkController/WorkControllerService for background task orchestration
- Worker classes (BaseTaskWorker, ForegroundWorker, BackgroundWorker)
- Task management (Task, TaskWorkerImpl, WorkerImpl, Observable/Observer)
- Notification system (Builder, Manager, NotificationRef, Notifier)
- Utility classes (AuthUtils, ContextUtil, NetworkUtils, ShareUtils)

Python application layer:
- main.py entry point for Kolibri server lifecycle
- taskworker.py and task_reconciler.py for background task execution
- auth.py for authentication token generation
- Kolibri plugin and settings modules
- monkey_patch_zeroconf.py for Android compatibility

Android resources:
- WebView layout with animated splash screen
- Kolibri logo vector drawable with wing-flap animation
- Notification icons, themes, colors
- Network security config, file provider paths
- Localized HTML content for loading/error screens

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add build_and_test.yml for running unit tests in CI
- Update build_apk.yml and release_apk.yml for Gradle-based builds
- Remove pr_build.yml (replaced by build_and_test.yml)
- Update pre-commit.yml and .pre-commit-config.yaml (add ruff, spotless)
- Overhaul Makefile for Gradle/Chaquopy workflow (SDK setup, emulator,
  build, install, test targets)
- Simplify scripts/version.py, create_strings.py, play_store_api.py
  for the new project structure

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
rtibbles and others added 3 commits February 23, 2026 07:19
- AuthUtilsTest: token generation, URL building, header extraction
- ContextUtilTest: singleton initialization and context access
- ShareUtilsTest: share intent construction
- BaseTaskWorkerTest: Python task delegation and error handling

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rewrite documentation to reflect the new build system, project
structure, and development workflow. Covers Gradle setup, Chaquopy
Python integration, emulator usage, and CI configuration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- CLAUDE.md and AGENTS.md for AI-assisted development workflow
- CDP helper (scripts/cdp_helper.py) for WebView DOM inspection and
  interaction via Chrome DevTools Protocol over ADB
- Unit tests for CDP helper
- Maestro smoke test for app launch verification

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove the apt dependencies step (buildozer-era packages not needed by
Chaquopy) and the make setup step (SDK, NDK, build-tools, platform-tools
are all preinstalled on ubuntu-latest). The runner provides
ANDROID_SDK_ROOT which the Makefile already respects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rtibbles rtibbles marked this pull request as ready for review February 23, 2026 15:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tracking Issue: Migrate App to use chaquopy

2 participants