|
1 | | -# Kolibri Android Installer |
| 1 | +# Kolibri Android |
2 | 2 |
|
3 | | -Wraps Kolibri in an android-compatibility layer. Relies on Python-For-Android to build the APK and for compatibility on the Android platform. |
| 3 | +Android application for Kolibri Learning Platform using Chaquopy for Python integration. |
4 | 4 |
|
5 | | -## Development Flow |
| 5 | +## Overview |
6 | 6 |
|
7 | | -1. Setup a Python virtual environment in which to do development. The Kolibri developer documentation has a [How To guide for doing this with pyenv](https://kolibri-dev.readthedocs.io/en/develop/howtos/pyenv_virtualenv.html) but any Python virtualenv should work. |
| 7 | +This project packages [Kolibri](https://learningequality.org/kolibri/) as an Android application using: |
| 8 | +- **Chaquopy** - Runs Python code directly on Android |
| 9 | +- **HTTP Server + Service Worker** - Python runs an HTTP server, WebView loads content, Service Worker handles caching |
| 10 | +- **WorkManager** - Handles background tasks in a separate process |
| 11 | +- **Modern Android Architecture** - Clean package structure, lifecycle-aware components |
8 | 12 |
|
9 | | -2. Ensure you have all [necessary packages for Python for Android](https://python-for-android.readthedocs.io/en/latest/quickstart.html#installing-prerequisites). Ensure you install java version 1.17, `sudo apt install openjdk-17-jdk` , and set it as the default java version: `sudo update-alternatives --auto javac` and `sudo update-alternatives --auto java`. |
| 13 | +## Quick Start |
10 | 14 |
|
11 | | -3. The `make setup` command will install the Android SDK and Android NDK. |
| 15 | +```bash |
| 16 | +# 1. Ensure Java 17+ is installed |
| 17 | +java -version |
12 | 18 |
|
13 | | -N.B. if you would like these to be installed to a different location then you can set an environment variable, e.g.: |
14 | | -By default it is set to `export ANDROID_SDK_ROOT=./android_root` |
| 19 | +# 2. Setup Python environment (recommended) |
| 20 | +python3 -m venv .venv && source .venv/bin/activate |
| 21 | +pip install -r build-requirements.txt -r requirements.txt |
15 | 22 |
|
16 | | -Run `make setup`. |
| 23 | +# 3. Setup Android SDK and emulator |
| 24 | +make setup |
17 | 25 |
|
18 | | -4. Install the Python dependencies: |
| 26 | +# 4. Download Kolibri tar file |
| 27 | +make get-tar tar=https://github.com/learningequality/kolibri/releases/download/v0.17.0/kolibri-0.17.0.tar.gz |
19 | 28 |
|
20 | | -`pip install -r requirements.txt` |
| 29 | +# 5. Build! |
| 30 | +make kolibri.apk.unsigned |
| 31 | +``` |
| 32 | + |
| 33 | +Output: `dist/kolibri-*.apk` |
| 34 | + |
| 35 | +## Development Setup |
| 36 | + |
| 37 | +### Prerequisites |
| 38 | + |
| 39 | +- **Java Development Kit (JDK) 17+** |
| 40 | + - Fedora/RHEL: `sudo dnf install java-17-openjdk-devel` |
| 41 | + - Ubuntu/Debian: `sudo apt install openjdk-17-jdk` |
| 42 | + - macOS: `brew install openjdk@17` |
21 | 43 |
|
22 | | -5. Build or download a Kolibri tar file, and place it in the `tar/` directory. |
| 44 | +- **Python 3.10+** - For build scripts and Chaquopy |
23 | 45 |
|
24 | | -To download a Kolibri WHL file, you can use `make get-tar tar=<URL>` from the command line. It will download it and put it in the correct directory. |
| 46 | +### Initial Setup |
25 | 47 |
|
26 | | -6. By default the APK/AAB will be built for most architectures supported by Python for Android. To build for a smaller set of architectures, set the `ARCHES` environment variable. Run `p4a archs` to see the available targets. |
| 48 | +1. **Clone the repository** |
| 49 | + ```bash |
| 50 | + git clone https://github.com/learningequality/kolibri-installer-android.git |
| 51 | + cd kolibri-installer-android |
| 52 | + ``` |
27 | 53 |
|
28 | | -7. Run `make p4a_android_project` this will do all of the Python for Android setup up. After, you can run `make kolibri.apk` or `make kolibri.apk.unsigned` if you want to build the apk in the console. |
| 54 | +2. **Set up Python virtual environment** |
| 55 | + ```bash |
| 56 | + python3 -m venv .venv |
| 57 | + source .venv/bin/activate |
| 58 | + pip install -r build-requirements.txt -r requirements.txt |
| 59 | + ``` |
29 | 60 |
|
30 | | -N.B. You will need to rerun this step any time you update the Kolibri WHL file you are using, or any time you update the Python code in this repository. |
| 61 | +3. **Set up Android SDK** (automatically downloads SDK, NDK, emulator) |
| 62 | + ```bash |
| 63 | + make setup |
| 64 | + ``` |
31 | 65 |
|
32 | | -8. You can now run Android Studio and open the folder `python-for-android/dists/kolibri` as the project folder to work from. You should be able to make updates to Java code, resource files, etc. using Android Studio, and build and run the project using Android Studio, including launching into emulators and real physical devices. |
| 66 | +4. **Get Kolibri tar file** |
| 67 | + ```bash |
| 68 | + make get-tar tar=<URL_TO_KOLIBRI_TAR> |
| 69 | + ``` |
33 | 70 |
|
34 | | -N.B. When you rerun step 7, it will complain loudly and exit early if you have uncommitted changes in the python-for-android folder. Any changes should be committed (even if in a temporary commit) before rerunning this step, as we use git stash to undo any changes in the Android project caused by the Python for Android project bootstrapping process. Also, when rerunning step 5, the Android version will not have incremented, meaning that any emulator or physical device will need to have Kolibri explicitly uninstalled for any changes to Python code to be updated on install. |
| 71 | +5. **Build the APK** |
| 72 | + ```bash |
| 73 | + make kolibri.apk.unsigned |
| 74 | + ``` |
35 | 75 |
|
36 | | -## Debugging the app |
| 76 | +## Architecture |
37 | 77 |
|
38 | | -1. When running the app from Android Studio, if you are using an emulator, it is possible that there will be many warning messages due to GPU emulation. In the logcat tab, update the filter to this `package:mine & -tag:eglCodecCommon` to hide those errors from the logcat output. |
| 78 | +The app runs Kolibri as an HTTP server in Python, with a WebView displaying the UI: |
39 | 79 |
|
40 | | -## Building from the commandline |
| 80 | +``` |
| 81 | +┌─────────────────────────────────────────────────────────────┐ |
| 82 | +│ Main Process │ |
| 83 | +│ ┌────────────────┐ ┌──────────────────┐ │ |
| 84 | +│ │ WebViewActivity│ │ KolibriServer │ │ |
| 85 | +│ │ │ │ Service │ │ |
| 86 | +│ │ ┌──────────┐ │ │ ┌────────────┐ │ │ |
| 87 | +│ │ │ WebView │──┼─ HTTP ──┼─▶│ Python │ │ │ |
| 88 | +│ │ │ │ │ │ │ HTTP │ │ │ |
| 89 | +│ │ │ Service │ │ │ │ Server │ │ │ |
| 90 | +│ │ │ Worker │ │ │ │ (Kolibri) │ │ │ |
| 91 | +│ │ └──────────┘ │ │ └────────────┘ │ │ |
| 92 | +│ └────────────────┘ └──────────────────┘ │ |
| 93 | +└─────────────────────────────────────────────────────────────┘ |
| 94 | +
|
| 95 | +┌─────────────────────────────────────────────────────────────┐ |
| 96 | +│ :task_worker Process │ |
| 97 | +│ ┌────────────────┐ ┌──────────────────┐ │ |
| 98 | +│ │ WorkController │────────▶│ WorkManager │ │ |
| 99 | +│ │ Service │ │ Workers │ │ |
| 100 | +│ └────────────────┘ └──────────────────┘ │ |
| 101 | +└─────────────────────────────────────────────────────────────┘ |
| 102 | +``` |
| 103 | + |
| 104 | +### Key Components |
41 | 105 |
|
42 | | -1. Run `make kolibri.apk.unsigned` to build the development apk. Watch for success at the end, or errors, which might indicate missing build dependencies or build errors. If successful, there should be an APK in the `dist/` directory. |
| 106 | +- **KolibriServerService** - Starts Python HTTP server in background |
| 107 | +- **WebViewActivity** - Displays Kolibri UI, requests notification permission |
| 108 | +- **WorkManager** - Runs background tasks (imports, syncs) in separate process |
| 109 | +- **Task Reconciler** - Syncs WorkManager state with Kolibri's job database |
43 | 110 |
|
44 | | -## Installing the apk |
45 | | -1. Connect your Android device over USB, with USB Debugging enabled. |
| 111 | +## Project Structure |
46 | 112 |
|
47 | | -2. Ensure that `adb devices` brings up your device. Afterward, run `make install` to install onto the device. |
| 113 | +``` |
| 114 | +app/src/main/ |
| 115 | +├── java/org/learningequality/Kolibri/ |
| 116 | +│ ├── App.java # Application entry point |
| 117 | +│ ├── WebViewActivity.java # Main activity with WebView |
| 118 | +│ ├── KolibriServerService.java # HTTP server service |
| 119 | +│ ├── KolibriServerViewModel.java # Server state management |
| 120 | +│ ├── KolibriEnvironmentSetup.java # Environment configuration |
| 121 | +│ ├── WorkController.java # Task scheduling |
| 122 | +│ ├── WorkControllerService.java # Worker process IPC |
| 123 | +│ ├── notification/ # Notification system |
| 124 | +│ ├── task/ # Task worker interfaces |
| 125 | +│ │ ├── Task.java # WorkManager wrapper |
| 126 | +│ │ └── TaskWorkerImpl.java # Observer pattern for progress |
| 127 | +│ ├── workers/ # WorkManager workers |
| 128 | +│ │ ├── BaseTaskWorker.java # Base class with notifications |
| 129 | +│ │ ├── ForegroundWorker.java # Long-running tasks |
| 130 | +│ │ └── BackgroundWorker.java # Short tasks |
| 131 | +│ └── util/ # Utility classes |
| 132 | +└── python/ |
| 133 | + ├── main.py # HTTP server entry point |
| 134 | + ├── android_utils.py # Android-specific utilities |
| 135 | + ├── taskworker.py # Task execution |
| 136 | + ├── task_reconciler.py # Task state reconciliation |
| 137 | + ├── task_status.py # Task status updates |
| 138 | + └── android_app_plugin/ # Kolibri plugin for Android |
| 139 | + └── kolibri_plugin.py # StorageHook for task scheduling |
| 140 | +``` |
48 | 141 |
|
| 142 | +## Building |
49 | 143 |
|
50 | | -## Running the apk from the terminal |
| 144 | +### Debug Build |
51 | 145 |
|
52 | | -1. Run `adb shell am start -n org.learningequality.Kolibri/org.kivy.android.PythonActivity` |
| 146 | +```bash |
| 147 | +make kolibri.apk.unsigned |
| 148 | +``` |
53 | 149 |
|
54 | | -### Server Side |
55 | | -Run `adb logcat -v brief python:D *:F` to get all debug logs from the Kolibri server |
| 150 | +Output: `dist/kolibri-*.apk` |
56 | 151 |
|
57 | | -### Client side |
58 | | -1. Start the Kolibri server via Android app |
59 | | -2. Open a browser and see debug logs |
60 | | - - If your device doesn't aggressively kill the server, you can open Chrome and use remote debugging tools to see the logs on your desktop. |
61 | | - - You can also leave the app open and port forward the Android device's Kolibri port using [adb](https://developer.android.com/studio/command-line/adb#forwardports): |
62 | | - ``` |
63 | | - adb forward tcp:8080 tcp:8081 |
64 | | - ``` |
65 | | - then going into your desktop's browser and accessing `localhost:8081`. Note that you can map to any port on the host machine, the second argument. |
| 152 | +### Release Build |
66 | 153 |
|
67 | | -Alternatively, you can debug the webview directly. Modern Android versions should let you do so from the developer settings. |
| 154 | +```bash |
| 155 | +export RELEASE_KEYSTORE=/path/to/keystore.jks |
| 156 | +export RELEASE_KEYALIAS=key-alias |
| 157 | +export RELEASE_KEYSTORE_PASSWD=keystore-password |
| 158 | +export RELEASE_KEYALIAS_PASSWD=key-password |
| 159 | +make kolibri.apk |
| 160 | +``` |
68 | 161 |
|
69 | | -You could also do so using [Weinre](https://people.apache.org/~pmuellr/weinre/docs/latest/Home.html). Visit the site to learn how to install and setup. You will have to build a custom Kolibri .whl file that contains the weinre script tag in the [base.html file](https://github.com/learningequality/kolibri/blob/develop/kolibri/core/templates/kolibri/base.html). |
| 162 | +### All Make Targets |
| 163 | + |
| 164 | +```bash |
| 165 | +make help # Show all available targets |
| 166 | +make setup # Setup SDK, NDK, and emulator |
| 167 | +make kolibri.apk.unsigned # Build debug APK |
| 168 | +make kolibri.apk # Build release APK (requires signing keys) |
| 169 | +make kolibri.aab # Build release AAB for Play Store |
| 170 | +make install # Install to connected device/emulator |
| 171 | +make test # Run unit tests |
| 172 | +make lint # Run Android linter |
| 173 | +make emulator # Start the emulator |
| 174 | +make logcat # View Kolibri-specific logs |
| 175 | +make clean # Clean build artifacts |
| 176 | +``` |
70 | 177 |
|
| 178 | +## Testing |
71 | 179 |
|
72 | | -## Helpful commands |
73 | | -- [adb](https://developer.android.com/studio/command-line/adb) is pretty helpful. Here are some useful uses: |
74 | | - - `adb logcat -b all -c` will clear out the device's log. ([Docs](https://developer.android.com/studio/command-line/logcat)) |
75 | | - - Logcat also has a large variety of filtering options. Check out the docs for those. |
76 | | - - Uninstall from terminal using `adb shell pm uninstall org.learningequality.Kolibri`. ([Docs](https://developer.android.com/studio/command-line/adb#pm)) |
77 | | -- Docker shouldn't be rebuilding very often, so it shouldn't be using that much storage. But if it does, you can run `docker system prune` to clear out all "dangling" images, containers, and layers. If you've been constantly rebuilding, it will likely get you several gigabytes of storage. |
| 180 | +```bash |
| 181 | +# Run unit tests |
| 182 | +make test |
78 | 183 |
|
79 | | -## Build on Docker |
| 184 | +# Start emulator and install |
| 185 | +make emulator |
| 186 | +make install |
80 | 187 |
|
81 | | -This project was previously developed on Docker, but this method has not recently been tested. |
| 188 | +# View logs |
| 189 | +make logcat |
| 190 | +``` |
82 | 191 |
|
83 | | -1. Install [docker](https://www.docker.com/community-edition) |
| 192 | +## Debugging |
84 | 193 |
|
85 | | -2. Build or download a Kolibri WHL file, and place in the `whl/` directory. |
| 194 | +```bash |
| 195 | +# Kolibri logs |
| 196 | +adb logcat -s Kolibri*:V |
86 | 197 |
|
87 | | -3. Run `make run_docker`. |
| 198 | +# Python logs |
| 199 | +adb logcat -v brief python:D *:F |
88 | 200 |
|
89 | | -4. The generated APK will end up in the `bin/` folder. |
| 201 | +# Task worker process |
| 202 | +adb logcat --pid=$(adb shell pidof -s org.learningequality.Kolibri:task_worker) |
90 | 203 |
|
91 | | -## Docker Implementation Notes |
92 | | -The image was optimized to limit rebuilding and to be run in a developer-centric way. `scripts/rundocker.sh` describes the options needed to get the build running properly. |
| 204 | +# WebView debugging: Chrome → chrome://inspect |
| 205 | +``` |
93 | 206 |
|
94 | | -Unless you need to make edits to the build method or are debugging one of the build dependencies and would like to continue using docker, you shouldn't need to modify that script. |
| 207 | +## Common Issues |
95 | 208 |
|
96 | | -## Getting a Python shell within the running app context |
| 209 | +**Build fails**: Run `make setup` to ensure SDK is configured correctly |
| 210 | +```bash |
| 211 | +make clean && make setup |
| 212 | +``` |
97 | 213 |
|
98 | | -We implemented code for an SSH server that allows connecting into a running Kolibri Android app and running code in an interactive Python shell. You can use this for developing, testing, and debugging Python code running inside the Android and Kolibri environments, which is handy especially for testing out Pyjnius code, checking environment variables, etc. This will soon be implemented as an Android service that can be turned on over ADB, but in the meantime you can use it a bit like you might use `import ipdb; ipdb.set_trace()` to get an interactive shell at a particular context in your code, as follows: |
| 214 | +**No notifications**: Grant notification permission in Android settings (required on Android 13+) |
99 | 215 |
|
100 | | -- Drop `import remoteshell` at the spot you want to have the shell get dropped in, and build/run the app. |
101 | | -- Connect the device over ADB, e.g. via USB. |
102 | | -- Run `adb forward tcp:4242 tcp:4242` (needs to be re-run if you disconnect and reconnect the device) |
103 | | -- Run `ssh -p 4242 localhost` |
104 | | -- If the device isn’t provisioned, any username/password will be accepted. Otherwise, use the admin credentials. |
105 | | -- If you get an error about “ssh-rsa”, you can put the following SSH config in: |
| 216 | +**Tasks not running**: Check WorkManager state |
| 217 | +```bash |
| 218 | +adb logcat -s BaseTaskWorker:V WorkController:V |
106 | 219 | ``` |
107 | | -Host kolibri-android |
108 | | - HostName localhost |
109 | | - Port 4242 |
110 | | - PubkeyAcceptedAlgorithms +ssh-rsa |
111 | | - HostkeyAlgorithms +ssh-rsa |
| 220 | + |
| 221 | +**Emulator won't start**: Check available AVDs and recreate if needed |
| 222 | +```bash |
| 223 | +make list-avds |
| 224 | +make avd # Recreate AVD |
112 | 225 | ``` |
113 | | -Then, you should be able to just do “ssh kolibri-android” |
114 | 226 |
|
115 | | -## Updating Python for Android |
| 227 | +## Contributing |
| 228 | + |
| 229 | +1. Create feature branch |
| 230 | +2. Make changes |
| 231 | +3. Run linting: `pre-commit run --all-files` |
| 232 | +4. Run tests: `make test` |
| 233 | +5. Submit pull request |
| 234 | + |
| 235 | +## License |
| 236 | + |
| 237 | +See [LICENSE](LICENSE) file. |
116 | 238 |
|
117 | | -We maintain a fork of Python for Android that includes various changes we have made to the source code to support our specific needs. As P4A make new releases, we make a branch from the latest release tag, and then replay the commits on top of this tag using an interactive rebase. Sometimes, this allows us to drop commits as new features are merged into P4A. Our naming convention for the branch on our fork is `from_upstream_<tag_name>`. Any time we push new commits to this branch, we must also update the pinned commit in `requirements.txt`, so that we are always building with a completely predictable version of Python for Android. |
| 239 | +## Support |
118 | 240 |
|
119 | | -By default we stash any updates to our bootstrap coming from Python for Android, because mostly we have overwritten their bootstrap code to make the relevant changes for us. If there are upstream changes to code we have committed in this repo from the bootstraps, then if the diff is small, it is probably simplest to manually copy in these changes to our committed code. If the diff is larger, or the developer fancies exercising some git-fu, then the make command `make update_project_from_p4a` will update the bootstrap from Python for Android, and not stash any changes that introduces. Through judicious change reversion and diffing, the appropriate changes can then be applied. Here be dragons. |
| 241 | +- **Issues**: https://github.com/learningequality/kolibri-installer-android/issues |
| 242 | +- **Kolibri Docs**: https://kolibri.readthedocs.io/ |
| 243 | +- **Chaquopy Docs**: https://chaquo.com/chaquopy/doc/current/ |
0 commit comments