Skip to content

Commit a9d4549

Browse files
author
lanmao
committed
Merge remote-tracking branch 'upstream/develop' into fix-android-qt-libmain
2 parents 428d2b7 + 70f9224 commit a9d4549

10 files changed

Lines changed: 454 additions & 24 deletions

File tree

doc/source/apis.rst

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,40 @@ Working on Android
55
This page gives details on accessing Android APIs and managing other
66
interactions on Android.
77

8+
Handling system bars and Edge-to-Edge enforcement
9+
-------------------------------------------------
10+
11+
**Egde-to-Edge is enforced on all android apis >=35 by default i.e. Android 15 and above.**
12+
13+
You can control the overall layout and system bars appearance in following ways::
14+
15+
from android.display_cutout import update_system_ui
16+
17+
update_system_ui(
18+
"#0f62fe", # status_bar_color: hex color code or rgba (tuple or list) values
19+
[0.059, 0.384, 0.996, 1.000], # navigation_bar_color: hex color code or rgba (tuple or list) values
20+
"Light", # icon_style: "Dark" means dark icons will be drawn, "Light" means light icons will be drawn, Literal["Dark", or "Light"]
21+
True, # pad_status: Adds a padding to top of content_view, Will take effect on Android 15+
22+
True, # pad_nav: Adds a padding to bottom of content_view, Will take effect on Android 15+
23+
)
24+
25+
26+
Handling Immersive Mode
27+
~~~~~~~~~~~~~~~~~~~~~~~
28+
29+
Immersive mode allows your application to hide the system bars (status bar and navigation bar) for a true full-screen experience, commonly used in games or media players.
30+
31+
You can control the immersive mode behavior and how system bars reappear using the following way::
32+
33+
from android.display_cutout import set_immersive_mode
34+
35+
set_immersive_mode(
36+
False, # hide_status: True explicitly targets hiding the top status bar, False leaves it visible
37+
False, # hide_nav: True explicitly targets hiding the bottom navigation bar/gestural pill, False leaves it visible
38+
True, # remove_contrast: True disables the default system-enforced background scrim/contrast behind the bars, ensuring complete transparency when they peek
39+
)
40+
41+
842
Storage paths
943
-------------
1044

@@ -80,6 +114,17 @@ https://developer.android.com/reference/android/Manifest.permission
80114
Other common tasks
81115
------------------
82116

117+
Using NumPY
118+
~~~~~~~~~~~
119+
120+
It should work out of the box; just ensure `numpy` is included in your requirements.
121+
If you want better performance, you can also add `libopenblas` to the requirements.
122+
This allows NumPy to use BLAS/LAPACK acceleration, which can significantly improve linear algebra operations.
123+
124+
Keep in mind that `libopenblas` is a fairly large library (~12 MB per architecture),
125+
so it will increase your app size. For example, if you build for 4 architectures,
126+
the APK size may increase by roughly `12 × 4 = 48 MB` (before compression).
127+
83128
Running executables
84129
~~~~~~~~~~~~~~~~~~~
85130

@@ -388,6 +433,36 @@ This can be used to prevent errors like:
388433
Because the python function is called from the PythonActivity thread, you
389434
need to be careful about your own calls.
390435

436+
Handling DarkMode
437+
~~~~~~~~~~~~~~~~~
438+
439+
The ``android.darkmode`` module provides functionality to detect and respond to
440+
system dark mode changes on Android devices.
441+
442+
You can set up a listener to monitor dark mode state changes using the
443+
``set_dark_mode_listener`` function::
444+
445+
from android.darkmode import set_dark_mode_listener
446+
447+
def on_dark_mode_changed(is_dark_mode):
448+
if is_dark_mode:
449+
print('Dark mode is now enabled')
450+
# Update your app's theme to dark mode
451+
else:
452+
print('Dark mode is now disabled')
453+
# Update your app's theme to light mode
454+
455+
# Register the listener
456+
set_dark_mode_listener(on_dark_mode_changed)
457+
458+
To remove the listener, simply pass ``None``::
459+
460+
set_dark_mode_listener(None)
461+
462+
The callback function receives a single boolean parameter ``is_dark_mode`` that
463+
indicates whether dark mode is currently enabled (``True``) or disabled (``False``).
464+
465+
391466

392467
Advanced Android API use
393468
------------------------

pythonforandroid/bootstraps/_sdl_common/build/templates/strings.tmpl.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<style name="KivySupportCutout">
44
<item name="android:windowNoTitle">true</item>
55
<!-- Display cutout is an area on some devices that extends into the display surface -->
6-
{% if args.display_cutout != 'never'%}
6+
{% if args.display_cutout != 'never' %}
77
<item name="android:windowLayoutInDisplayCutoutMode">{{ args.display_cutout }}</item>
88
<item name="android:windowTranslucentStatus">true</item>
99
<item name="android:windowTranslucentNavigation">true</item>

pythonforandroid/bootstraps/qt/build/src/main/java/org/kivy/android/PythonActivity.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import android.content.Context;
44
import android.content.Intent;
55
import android.content.pm.PackageManager;
6+
import android.content.res.Configuration;
67
import android.os.Bundle;
78
import android.os.PowerManager;
89
import android.os.SystemClock;
@@ -233,4 +234,26 @@ public static void _do_start_service(
233234
public static void stop_service() {
234235
PythonServiceIntent.stop(PythonActivity.mActivity, PythonService.class);
235236
}
237+
238+
public interface DarkModeListener {
239+
void onDarkModeChanged(boolean isDarkMode);
240+
}
241+
242+
private DarkModeListener darkModeListener = null;
243+
244+
public void setDarkModeListener(DarkModeListener listener) {
245+
darkModeListener = listener;
246+
}
247+
248+
@Override
249+
public void onConfigurationChanged(Configuration newConfig) {
250+
int currentNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
251+
boolean isDarkMode = currentNightMode == Configuration.UI_MODE_NIGHT_YES;
252+
253+
if (darkModeListener != null) {
254+
darkModeListener.onDarkModeChanged(isDarkMode);
255+
}
256+
257+
super.onConfigurationChanged(newConfig);
258+
}
236259
}

pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import android.content.Intent;
66
import android.content.pm.ActivityInfo;
77
import android.content.pm.PackageManager;
8+
import android.content.res.Configuration;
89
import android.content.res.Resources.NotFoundException;
910
import android.graphics.Bitmap;
1011
import android.graphics.BitmapFactory;
@@ -615,6 +616,28 @@ public void requestPermissions(String[] permissions) {
615616
requestPermissionsWithRequestCode(permissions, 1);
616617
}
617618

619+
public interface DarkModeListener {
620+
void onDarkModeChanged(boolean isDarkMode);
621+
}
622+
623+
private DarkModeListener darkModeListener = null;
624+
625+
public void setDarkModeListener(DarkModeListener listener) {
626+
darkModeListener = listener;
627+
}
628+
629+
@Override
630+
public void onConfigurationChanged(Configuration newConfig) {
631+
int currentNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
632+
boolean isDarkMode = currentNightMode == Configuration.UI_MODE_NIGHT_YES;
633+
634+
if (darkModeListener != null) {
635+
darkModeListener.onDarkModeChanged(isDarkMode);
636+
}
637+
638+
super.onConfigurationChanged(newConfig);
639+
}
640+
618641
public static void changeKeyboard(int inputType) {
619642
if (SDLActivity.keyboardInputType != inputType) {
620643
SDLActivity.keyboardInputType = inputType;

pythonforandroid/bootstraps/sdl3/build/src/main/java/org/kivy/android/PythonActivity.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import android.content.Intent;
66
import android.content.pm.ActivityInfo;
77
import android.content.pm.PackageManager;
8+
import android.content.res.Configuration;
89
import android.content.res.Resources.NotFoundException;
910
import android.graphics.Bitmap;
1011
import android.graphics.BitmapFactory;
@@ -614,6 +615,28 @@ public void requestPermissions(String[] permissions) {
614615
requestPermissionsWithRequestCode(permissions, 1);
615616
}
616617

618+
public interface DarkModeListener {
619+
void onDarkModeChanged(boolean isDarkMode);
620+
}
621+
622+
private DarkModeListener darkModeListener = null;
623+
624+
public void setDarkModeListener(DarkModeListener listener) {
625+
darkModeListener = listener;
626+
}
627+
628+
@Override
629+
public void onConfigurationChanged(Configuration newConfig) {
630+
int currentNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
631+
boolean isDarkMode = currentNightMode == Configuration.UI_MODE_NIGHT_YES;
632+
633+
if (darkModeListener != null) {
634+
darkModeListener.onDarkModeChanged(isDarkMode);
635+
}
636+
637+
super.onConfigurationChanged(newConfig);
638+
}
639+
617640
public static void changeKeyboard(int inputType) {
618641
/*
619642
if (SDLActivity.keyboardInputType != inputType){

pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/PythonActivity.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import android.content.DialogInterface;
77
import android.content.Intent;
88
import android.content.pm.PackageManager;
9+
import android.content.res.Configuration;
910
import android.graphics.Bitmap;
1011
import android.graphics.BitmapFactory;
1112
import android.graphics.Color;
@@ -542,6 +543,28 @@ public void requestPermissionsWithRequestCode(String[] permissions, int requestC
542543
public void requestPermissions(String[] permissions) {
543544
requestPermissionsWithRequestCode(permissions, 1);
544545
}
546+
547+
public interface DarkModeListener {
548+
void onDarkModeChanged(boolean isDarkMode);
549+
}
550+
551+
private DarkModeListener darkModeListener = null;
552+
553+
public void setDarkModeListener(DarkModeListener listener) {
554+
darkModeListener = listener;
555+
}
556+
557+
@Override
558+
public void onConfigurationChanged(Configuration newConfig) {
559+
int currentNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
560+
boolean isDarkMode = currentNightMode == Configuration.UI_MODE_NIGHT_YES;
561+
562+
if (darkModeListener != null) {
563+
darkModeListener.onDarkModeChanged(isDarkMode);
564+
}
565+
566+
super.onConfigurationChanged(newConfig);
567+
}
545568
}
546569

547570
class PythonMain implements Runnable {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from typing import Callable
2+
3+
from jnius import PythonJavaClass, java_method, autoclass
4+
from android.config import ACTIVITY_CLASS_NAME, ACTIVITY_CLASS_NAMESPACE
5+
6+
_listener = None
7+
8+
9+
class DarkModeListener(PythonJavaClass):
10+
"""
11+
A listener class for detecting and handling dark mode changes.
12+
13+
This class implements the `DarkModeListener` interface in a Python-Java
14+
hybrid context through Kivy Android functionality. It listens for changes
15+
in the system's dark mode settings and executes a callback upon detecting
16+
a change.
17+
18+
Attributes:
19+
on_dark_mode_changed (Callable[[bool], None]): A callback function to
20+
handle the event when dark mode status changes. The callback
21+
receives a single parameter `is_dark_mode`, which is a boolean
22+
indicating whether dark mode is currently enabled.
23+
"""
24+
__javacontext__ = "app"
25+
__javainterfaces__ = [ACTIVITY_CLASS_NAMESPACE + '$DarkModeListener']
26+
27+
def __init__(self, on_dark_mode_changed: Callable[[bool], None]):
28+
self.on_dark_mode_changed = on_dark_mode_changed
29+
30+
@java_method('(Z)V')
31+
def onDarkModeChanged(self, is_dark_mode):
32+
self.on_dark_mode_changed(is_dark_mode)
33+
34+
35+
def set_dark_mode_listener(on_dark_mode_changed: Callable[[bool], None] | None) -> None:
36+
"""
37+
Sets a listener to monitor changes in the dark mode state.
38+
39+
This function assigns a provided callback to handle changes in the
40+
dark mode settings. The callback will be invoked with a boolean
41+
argument indicating the current dark mode state.
42+
43+
Args:
44+
on_dark_mode_changed: A callable that accepts a single boolean
45+
parameter indicating whether dark mode is active.
46+
47+
Returns:
48+
None
49+
"""
50+
global _listener
51+
activity = autoclass(ACTIVITY_CLASS_NAME).mActivity
52+
if on_dark_mode_changed:
53+
_listener = DarkModeListener(on_dark_mode_changed)
54+
activity.setDarkModeListener(_listener)
55+
else:
56+
activity.setDarkModeListener(on_dark_mode_changed)
57+
_listener = None

0 commit comments

Comments
 (0)