Skip to content

Commit 58e840c

Browse files
author
اشکان
committed
Fix android service containing PySide6 codes crashes the service due to Qt preparations not done and Qt libs not loaded
1 parent ad8f902 commit 58e840c

File tree

2 files changed

+236
-1
lines changed

2 files changed

+236
-1
lines changed
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
package org.kivy.android;
2+
3+
import android.os.Build;
4+
import java.lang.reflect.Method;
5+
import java.lang.reflect.InvocationTargetException;
6+
import android.app.Service;
7+
import android.os.IBinder;
8+
import android.os.Bundle;
9+
import android.content.Intent;
10+
import android.content.Context;
11+
import android.util.Log;
12+
import android.app.Notification;
13+
import android.app.PendingIntent;
14+
import android.os.Process;
15+
import java.io.File;
16+
17+
//imports for channel definition
18+
import android.app.NotificationManager;
19+
import android.app.NotificationChannel;
20+
import android.graphics.Color;
21+
22+
import org.qtproject.qt.android.bindings.QtService;
23+
24+
public class PythonService extends QtService implements Runnable {
25+
private static final String TAG = "PythonQtService";
26+
27+
// Thread for Python code
28+
private Thread pythonThread = null;
29+
30+
// Python environment variables
31+
private String androidPrivate;
32+
private String androidArgument;
33+
private String pythonName;
34+
private String pythonHome;
35+
private String pythonPath;
36+
private String serviceEntrypoint;
37+
// Argument to pass to Python code,
38+
private String pythonServiceArgument;
39+
40+
public static PythonService mService = null;
41+
private Intent startIntent = null;
42+
43+
private boolean autoRestartService = false;
44+
45+
public void setEnvironmentVariable(String key, String value) {
46+
/**
47+
* Sets an environment variable based on key/value.
48+
**/
49+
try {
50+
android.system.Os.setenv(key, value, true);
51+
} catch (Exception e) {
52+
Log.e(TAG, "Unable set environment variable:" + key + "=" + value);
53+
e.printStackTrace();
54+
}
55+
}
56+
57+
public void setAutoRestartService(boolean restart) {
58+
autoRestartService = restart;
59+
}
60+
61+
public int startType() {
62+
return START_NOT_STICKY;
63+
}
64+
65+
@Override
66+
public IBinder onBind(Intent arg0) {
67+
return null;
68+
}
69+
70+
@Override
71+
public void onCreate() {
72+
}
73+
74+
@Override
75+
public int onStartCommand(Intent intent, int flags, int startId) {
76+
if (pythonThread != null) {
77+
Log.v(TAG, "service exists, do not start again");
78+
return startType();
79+
}
80+
//intent is null if OS restarts a STICKY service
81+
if (intent == null) {
82+
Context context = getApplicationContext();
83+
intent = getThisDefaultIntent(context, "");
84+
}
85+
86+
startIntent = intent;
87+
Bundle extras = intent.getExtras();
88+
androidPrivate = extras.getString("androidPrivate");
89+
androidArgument = extras.getString("androidArgument");
90+
serviceEntrypoint = extras.getString("serviceEntrypoint");
91+
pythonName = extras.getString("pythonName");
92+
pythonHome = extras.getString("pythonHome");
93+
pythonPath = extras.getString("pythonPath");
94+
boolean serviceStartAsForeground = (
95+
extras.getString("serviceStartAsForeground").equals("true")
96+
);
97+
pythonServiceArgument = extras.getString("pythonServiceArgument");
98+
pythonThread = new Thread(this);
99+
pythonThread.start();
100+
101+
if (serviceStartAsForeground) {
102+
doStartForeground(extras);
103+
}
104+
105+
return startType();
106+
}
107+
108+
protected int getServiceId() {
109+
return 1;
110+
}
111+
112+
protected Intent getThisDefaultIntent(Context ctx, String pythonServiceArgument) {
113+
return null;
114+
}
115+
116+
protected void doStartForeground(Bundle extras) {
117+
String serviceTitle = extras.getString("serviceTitle");
118+
String smallIconName = extras.getString("smallIconName");
119+
String contentTitle = extras.getString("contentTitle");
120+
String contentText = extras.getString("contentText");
121+
Notification notification;
122+
Context context = getApplicationContext();
123+
Intent contextIntent = new Intent(context, PythonActivity.class);
124+
PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent,
125+
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
126+
127+
// Unspecified icon uses default.
128+
int smallIconId = context.getApplicationInfo().icon;
129+
if (smallIconName != null) {
130+
if (!smallIconName.equals("")){
131+
int resId = getResources().getIdentifier(smallIconName, "mipmap",
132+
getPackageName());
133+
if (resId ==0) {
134+
resId = getResources().getIdentifier(smallIconName, "drawable",
135+
getPackageName());
136+
}
137+
if (resId !=0) {
138+
smallIconId = resId;
139+
}
140+
}
141+
}
142+
143+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
144+
// This constructor is deprecated
145+
notification = new Notification(
146+
smallIconId, serviceTitle, System.currentTimeMillis());
147+
try {
148+
// prevent using NotificationCompat, this saves 100kb on apk
149+
Method func = notification.getClass().getMethod(
150+
"setLatestEventInfo", Context.class, CharSequence.class,
151+
CharSequence.class, PendingIntent.class);
152+
func.invoke(notification, context, contentTitle, contentText, pIntent);
153+
} catch (NoSuchMethodException | IllegalAccessException |
154+
IllegalArgumentException | InvocationTargetException e) {
155+
}
156+
} else {
157+
// for android 8+ we need to create our own channel
158+
// https://stackoverflow.com/questions/47531742/startforeground-fail-after-upgrade-to-android-8-1
159+
String NOTIFICATION_CHANNEL_ID = "org.kivy.p4a" + getServiceId();
160+
String channelName = "Background Service" + getServiceId();
161+
NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_NONE);
162+
163+
chan.setLightColor(Color.BLUE);
164+
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
165+
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
166+
manager.createNotificationChannel(chan);
167+
168+
Notification.Builder builder = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID);
169+
builder.setContentTitle(contentTitle);
170+
builder.setContentText(contentText);
171+
builder.setContentIntent(pIntent);
172+
builder.setSmallIcon(smallIconId);
173+
notification = builder.build();
174+
}
175+
startForeground(getServiceId(), notification);
176+
}
177+
178+
@Override
179+
public void onDestroy() {
180+
super.onDestroy();
181+
pythonThread = null;
182+
if (autoRestartService && startIntent != null) {
183+
Log.v(TAG, "service restart requested");
184+
startService(startIntent);
185+
}
186+
Process.killProcess(Process.myPid());
187+
}
188+
189+
/**
190+
* Stops the task gracefully when killed.
191+
* Calling stopSelf() will trigger a onDestroy() call from the system.
192+
*/
193+
@Override
194+
public void onTaskRemoved(Intent rootIntent) {
195+
super.onTaskRemoved(rootIntent);
196+
//sticky service runtime/restart is managed by the OS. leave it running when app is closed
197+
if (startType() != START_STICKY) {
198+
stopSelf();
199+
}
200+
}
201+
202+
@Override
203+
public void run(){
204+
String app_root = getFilesDir().getAbsolutePath() + "/app";
205+
File app_root_file = new File(app_root);
206+
PythonUtil.loadLibraries(app_root_file,
207+
new File(getApplicationInfo().nativeLibraryDir));
208+
this.mService = this;
209+
210+
Log.v(TAG, "Setting env vars for start.c and Python to use");
211+
setEnvironmentVariable("ANDROID_ENTRYPOINT", app_root + "/" + serviceEntrypoint);
212+
setEnvironmentVariable("ANDROID_ARGUMENT", app_root);
213+
setEnvironmentVariable("ANDROID_APP_PATH", app_root);
214+
setEnvironmentVariable("ANDROID_PRIVATE", androidPrivate);
215+
setEnvironmentVariable("ANDROID_UNPACK", app_root);
216+
setEnvironmentVariable("PYTHONHOME", pythonHome);
217+
setEnvironmentVariable("PYTHONPATH", pythonPath + ":" + app_root + ":" + app_root + "/lib");
218+
setEnvironmentVariable("PYTHONOPTIMIZE", "2");
219+
220+
super.onCreate();
221+
222+
stopSelf();
223+
}
224+
225+
// Native part
226+
public static native void nativeStart(
227+
String androidPrivate, String androidArgument,
228+
String serviceEntrypoint, String pythonName,
229+
String pythonHome, String pythonPath,
230+
String pythonServiceArgument);
231+
}

pythonforandroid/bootstraps/qt/build/templates/AndroidManifest.tmpl.xml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,11 @@
9393
{% endif %}
9494
{% for name in service_names %}
9595
<service android:name="{{ args.package }}.Service{{ name|capitalize }}"
96-
android:process=":service_{{ name }}" />
96+
android:process=":service_{{ name }}"
97+
android:exported="true">
98+
<meta-data android:name="android.app.lib_name"
99+
android:value="main" />
100+
</service>
97101
{% endfor %}
98102
{% for name in native_services %}
99103
<service android:name="{{ name }}" />

0 commit comments

Comments
 (0)