From f97a26d49808b49f0f8f95d2ca5c922670968b59 Mon Sep 17 00:00:00 2001 From: Richard Tibbles Date: Thu, 4 Sep 2025 13:16:51 -0700 Subject: [PATCH 1/5] Update to newer (unreleased) Python for Android version. --- .p4a | 2 +- Makefile | 13 +++++++------ README.md | 2 +- requirements.txt | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.p4a b/.p4a index eee38235..a2c409e8 100644 --- a/.p4a +++ b/.p4a @@ -5,7 +5,7 @@ --dist_name "kolibri" --private "src" --requirements python3==3.9.13,hostpython3==3.9.13,android,pyjnius,genericndkbuild,sqlite3,cryptography,twisted,attrs,bcrypt,service_identity,pyasn1,pyasn1_modules,pyopenssl,openssl,six,kolibri,ifaddr ---android-api 33 +--android-api 35 --minsdk 23 --ndk-api 23 --permission ACCESS_NETWORK_STATE diff --git a/Makefile b/Makefile index 2c8406be..697ca1f5 100644 --- a/Makefile +++ b/Makefile @@ -17,8 +17,9 @@ else PLATFORM := linux endif -ANDROID_API := 33 -ANDROIDNDKVER := 25.2.9519653 +ANDROID_API := 35 +ANDROIDNDKVER := 28.2.13676358 +SDKMANAGER_VERSION := 13114758 ifdef ANDROID_SDK_ROOT else @@ -191,19 +192,19 @@ logcat: $(SDK)/cmdline-tools/latest/bin/sdkmanager: @echo "Downloading Android SDK command line tools" - wget https://dl.google.com/android/repository/commandlinetools-$(PLATFORM)-9477386_latest.zip + wget https://dl.google.com/android/repository/commandlinetools-$(PLATFORM)-${SDKMANAGER_VERSION}_latest.zip rm -rf cmdline-tools - unzip commandlinetools-$(PLATFORM)-9477386_latest.zip -d $(SDK) + unzip commandlinetools-$(PLATFORM)-${SDKMANAGER_VERSION}_latest.zip -d $(SDK) mv $(SDK)/cmdline-tools $(SDK)/latest mkdir -p $(SDK)/cmdline-tools mv $(SDK)/latest $(SDK)/cmdline-tools/latest - rm commandlinetools-$(PLATFORM)-9477386_latest.zip + rm commandlinetools-$(PLATFORM)-${SDKMANAGER_VERSION}_latest.zip sdk: $(SDK)/cmdline-tools/latest/bin/sdkmanager yes y | $(SDK)/cmdline-tools/latest/bin/sdkmanager "platform-tools" yes y | $(SDK)/cmdline-tools/latest/bin/sdkmanager "platforms;android-$(ANDROID_API)" yes y | $(SDK)/cmdline-tools/latest/bin/sdkmanager "system-images;android-$(ANDROID_API);default;x86_64" - yes y | $(SDK)/cmdline-tools/latest/bin/sdkmanager "build-tools;30.0.3" + yes y | $(SDK)/cmdline-tools/latest/bin/sdkmanager "build-tools;35.0.0" yes y | $(SDK)/cmdline-tools/latest/bin/sdkmanager "ndk;$(ANDROIDNDKVER)" ln -sfT ndk/$(ANDROIDNDKVER) $(SDK)/ndk-bundle @echo "Accepting all licenses" diff --git a/README.md b/README.md index c80b3adc..7a3955bc 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Wraps Kolibri in an android-compatibility layer. Relies on Python-For-Android to 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. -2. Ensure you have all [necessary packages for Python for Android](https://python-for-android.readthedocs.io/en/latest/quickstart/#installing-dependencies). Ensure you install java version 1.11, `sudo apt install openjdk-11-jdk` , and set it as the default java version: `sudo update-alternatives --auto javac` and `sudo update-alternatives --auto java`. +2. Ensure you have all [necessary packages for Python for Android](https://python-for-android.readthedocs.io/en/latest/quickstart/#installing-dependencies). 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`. 3. The `make setup` command will install the Android SDK and Android NDK. diff --git a/requirements.txt b/requirements.txt index 28a6fa28..ebcf79cc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ cython~=0.29 ifaddr virtualenv setuptools -git+https://github.com/learningequality/python-for-android@60f3bf7d653d3e4e97d8371feaef221ffcdeb421#egg=python-for-android +git+https://github.com/learningequality/python-for-android@632047c77661710e1ed19b110ef78402c4c64228#egg=python-for-android google-api-python-client==2.96.0 google-auth==2.22.0 google-auth-httplib2==0.1.0 From 85a9f80821000a3cee70b194675628773eef2b26 Mon Sep 17 00:00:00 2001 From: Richard Tibbles Date: Thu, 4 Sep 2025 13:17:06 -0700 Subject: [PATCH 2/5] Fix deprecation warnings from generating/generated strings. --- scripts/create_strings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/create_strings.py b/scripts/create_strings.py index 78242d0f..93f32726 100644 --- a/scripts/create_strings.py +++ b/scripts/create_strings.py @@ -20,7 +20,7 @@ XML_TEMPLATE = """ - @@ -53,7 +53,7 @@ def generate_loading_pages(output_dir): def _find_string(lang, string): from kolibri.main import initialize from django.utils.translation import override - from django.utils.translation import ugettext as _ + from django.utils.translation import gettext as _ from django.utils.translation import to_locale initialize(skip_update=True) From 2c4f7031de3cf9bf0649a53dd8c4d96fffb783af Mon Sep 17 00:00:00 2001 From: Richard Tibbles Date: Thu, 4 Sep 2025 13:18:34 -0700 Subject: [PATCH 3/5] Update build for Android 35 API targeting. Cleanup easily fixed gradle deprecation warnings. --- python-for-android/dists/kolibri/build.gradle | 47 ++++++++++--------- .../kolibri/src/main/AndroidManifest.xml | 6 +-- .../java/org/kivy/android/PythonUtil.java | 2 +- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/python-for-android/dists/kolibri/build.gradle b/python-for-android/dists/kolibri/build.gradle index e74b85c1..6dff46fb 100644 --- a/python-for-android/dists/kolibri/build.gradle +++ b/python-for-android/dists/kolibri/build.gradle @@ -2,20 +2,17 @@ buildscript { repositories { google() - jcenter() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.1.2' + classpath 'com.android.tools.build:gradle:8.13.0' } } allprojects { repositories { google() - jcenter() - flatDir { - dirs 'libs' - } + mavenCentral() } } @@ -24,7 +21,9 @@ apply plugin: 'com.android.application' android { - compileSdk 34 + namespace = 'org.learningequality.Kolibri' + compileSdkVersion = 35 + buildToolsVersion = "35.0.0" def versionPropsFile = file('version.properties') Properties versionProps = new Properties() @@ -45,21 +44,23 @@ android { def nameNoDebug = name.replace("-debug", "") defaultConfig { - minSdkVersion 23 - targetSdk 34 - versionCode code - versionName name + minSdkVersion = 23 + targetSdk = 35 + versionCode = code + versionName = name manifestPlaceholders = [:] - multiDexEnabled true + multiDexEnabled = true setProperty("archivesBaseName", "kolibri-$nameNoDebug") + buildConfigField "String", "VERSION_CODE", "\"${code.toString()}\"" } packagingOptions { jniLibs { useLegacyPackaging = true } - exclude 'lib/**/gdbserver' - exclude 'lib/**/gdb.setup' + resources { + excludes += ['lib/**/gdbserver', 'lib/**/gdb.setup'] + } } signingConfigs { @@ -73,30 +74,32 @@ android { buildTypes { debug { - debuggable true + debuggable = true } release { - signingConfig signingConfigs.release + signingConfig = signingConfigs.release } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } sourceSets { main { jniLibs.srcDir 'libs' - java { - } } } - aaptOptions { - noCompress "tflite" + + buildFeatures { + buildConfig = true + } + androidResources { + noCompress 'tflite' } } diff --git a/python-for-android/dists/kolibri/src/main/AndroidManifest.xml b/python-for-android/dists/kolibri/src/main/AndroidManifest.xml index a77a41ee..cba40cdd 100644 --- a/python-for-android/dists/kolibri/src/main/AndroidManifest.xml +++ b/python-for-android/dists/kolibri/src/main/AndroidManifest.xml @@ -2,7 +2,6 @@ @@ -38,7 +37,6 @@ android:theme="@android:style/Theme.NoTitleBar" android:hardwareAccelerated="true" android:usesCleartextTraffic="true" - android:extractNativeLibs="true" > @@ -82,14 +80,14 @@ android:name="androidx.work.multiprocess.RemoteWorkManagerService" android:process="@string/task_worker_process" android:exported="true" - tools:replace="android:process, android:exported" + tools:replace="android:exported" /> libsList, String pattern, File libsDir) { // pattern should be the name of the lib file, without the From db94570bbe5dc61e25aebf31ec0c35d403acea0a Mon Sep 17 00:00:00 2001 From: Richard Tibbles Date: Thu, 4 Sep 2025 13:19:11 -0700 Subject: [PATCH 4/5] Add conditional layout to handle Android 15+ edge to edge layout default. --- .../java/org/kivy/android/PythonActivity.java | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/python-for-android/dists/kolibri/src/main/java/org/kivy/android/PythonActivity.java b/python-for-android/dists/kolibri/src/main/java/org/kivy/android/PythonActivity.java index 5442f00b..33457e15 100644 --- a/python-for-android/dists/kolibri/src/main/java/org/kivy/android/PythonActivity.java +++ b/python-for-android/dists/kolibri/src/main/java/org/kivy/android/PythonActivity.java @@ -32,7 +32,11 @@ import android.graphics.Color; import android.widget.AbsoluteLayout; +import android.widget.FrameLayout; import android.view.ViewGroup.LayoutParams; +import android.view.WindowInsets; +import android.view.View; +import android.graphics.Insets; import android.webkit.WebBackForwardList; import android.webkit.WebViewClient; @@ -177,7 +181,6 @@ public void onClick(DialogInterface dialog,int id) { String unencodedHtml = PythonActivity.mActivity.getString(R.string.loading_page_html); String encodedHtml = Base64.encodeToString(unencodedHtml.getBytes(), Base64.NO_PADDING); mWebView.loadData(encodedHtml, "text/html", "base64"); - mWebView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); mWebView.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { @@ -200,8 +203,46 @@ public void onPageFinished(WebView view, String url) { } }); - mLayout = new AbsoluteLayout(PythonActivity.mActivity); - mLayout.addView(mWebView); + + // Handle edge-to-edge insets only on Android 15+ (API 35+) where edge-to-edge is enforced + if (android.os.Build.VERSION.SDK_INT >= 35) { + // Use FrameLayout for Android 15+ to properly support margins for edge-to-edge + mLayout = new FrameLayout(PythonActivity.mActivity); + + // Set WebView with FrameLayout params that support margins + FrameLayout.LayoutParams webViewParams = new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT + ); + mWebView.setLayoutParams(webViewParams); + mLayout.addView(mWebView); + + mLayout.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { + @Override + public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { + // Get system bar insets using the Android 11+ API + android.graphics.Insets systemBarsInsets = insets.getInsets(WindowInsets.Type.systemBars()); + + // Apply margins to the WebView to avoid system bars + FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mWebView.getLayoutParams(); + params.leftMargin = systemBarsInsets.left; + params.topMargin = systemBarsInsets.top; + params.rightMargin = systemBarsInsets.right; + params.bottomMargin = systemBarsInsets.bottom; + mWebView.setLayoutParams(params); + + // Return the insets unchanged to allow other views to also handle them + return insets; + } + }); + // Enable edge-to-edge but handle insets properly + mLayout.setFitsSystemWindows(false); + } else { + // For older Android versions, use the original AbsoluteLayout setup + mLayout = new AbsoluteLayout(PythonActivity.mActivity); + mWebView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); + mLayout.addView(mWebView); + } setContentView(mLayout); From 6036fab7db0597f7b277c667e32267d46bb8e9ee Mon Sep 17 00:00:00 2001 From: Richard Tibbles Date: Thu, 11 Sep 2025 13:12:01 -0700 Subject: [PATCH 5/5] Update p4a documentation link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7a3955bc..422ad770 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Wraps Kolibri in an android-compatibility layer. Relies on Python-For-Android to 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. -2. Ensure you have all [necessary packages for Python for Android](https://python-for-android.readthedocs.io/en/latest/quickstart/#installing-dependencies). 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`. +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`. 3. The `make setup` command will install the Android SDK and Android NDK.