Android service setup
This commit is contained in:
parent
64567710c3
commit
536c77396f
|
@ -22,6 +22,10 @@ patchsdl:
|
||||||
cp patches/HIDDeviceUSB.java .buildozer/android/platform/build-arm64-v8a/dists/sideband/src/main/java/org/libsdl/app/HIDDeviceUSB.java
|
cp patches/HIDDeviceUSB.java .buildozer/android/platform/build-arm64-v8a/dists/sideband/src/main/java/org/libsdl/app/HIDDeviceUSB.java
|
||||||
cp patches/HIDDeviceUSB.java .buildozer/android/platform/build-arm64-v8a/dists/sideband/jni/SDL/android-project/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java
|
cp patches/HIDDeviceUSB.java .buildozer/android/platform/build-arm64-v8a/dists/sideband/jni/SDL/android-project/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java
|
||||||
|
|
||||||
|
cp patches/PythonService.java .buildozer/android/platform/python-for-android/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonService.java
|
||||||
|
cp patches/PythonService.java .buildozer/android/platform/build-arm64-v8a/build/bootstrap_builds/sdl2/src/main/java/org/kivy/android/PythonService.java
|
||||||
|
cp patches/PythonService.java .buildozer/android/platform/build-arm64-v8a/dists/sideband/src/main/java/org/kivy/android/PythonService.java
|
||||||
|
|
||||||
injectxml:
|
injectxml:
|
||||||
mkdir -p .buildozer/android/platform/build-arm64-v8a/dists/sideband/src/main/res/xml
|
mkdir -p .buildozer/android/platform/build-arm64-v8a/dists/sideband/src/main/res/xml
|
||||||
mkdir -p .buildozer/android/platform/build-arm64-v8a/dists/sideband/templates
|
mkdir -p .buildozer/android/platform/build-arm64-v8a/dists/sideband/templates
|
||||||
|
|
|
@ -25,7 +25,7 @@ android.presplash_color = #00000000
|
||||||
orientation = all
|
orientation = all
|
||||||
fullscreen = 0
|
fullscreen = 0
|
||||||
|
|
||||||
android.permissions = INTERNET,POST_NOTIFICATIONS,WAKE_LOCK,FOREGROUND_SERVICE
|
android.permissions = INTERNET,POST_NOTIFICATIONS,WAKE_LOCK,FOREGROUND_SERVICE,CHANGE_WIFI_MULTICAST_STATE
|
||||||
android.api = 30
|
android.api = 30
|
||||||
android.minapi = 27
|
android.minapi = 27
|
||||||
android.ndk = 19b
|
android.ndk = 19b
|
||||||
|
@ -34,7 +34,7 @@ android.accept_sdk_license = True
|
||||||
android.arch = arm64-v8a
|
android.arch = arm64-v8a
|
||||||
#android.logcat_filters = *:S python:D
|
#android.logcat_filters = *:S python:D
|
||||||
|
|
||||||
# services = sidebandservice:services/sidebandservice.py:foreground
|
services = sidebandservice:services/sidebandservice.py:foreground
|
||||||
android.manifest.intent_filters = patches/intent-filter.xml
|
android.manifest.intent_filters = patches/intent-filter.xml
|
||||||
|
|
||||||
[buildozer]
|
[buildozer]
|
||||||
|
|
|
@ -96,6 +96,7 @@ class SidebandApp(MDApp):
|
||||||
self.guide_action()
|
self.guide_action()
|
||||||
|
|
||||||
self.app_state = SidebandApp.ACTIVE
|
self.app_state = SidebandApp.ACTIVE
|
||||||
|
self.start_android_service()
|
||||||
|
|
||||||
def start_android_service(self):
|
def start_android_service(self):
|
||||||
service = autoclass('io.unsigned.sideband.ServiceSidebandservice')
|
service = autoclass('io.unsigned.sideband.ServiceSidebandservice')
|
||||||
|
|
|
@ -0,0 +1,209 @@
|
||||||
|
package org.kivy.android;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.os.Process;
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
//imports for channel definition
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.drawable.Icon;
|
||||||
|
|
||||||
|
import android.net.wifi.WifiManager;
|
||||||
|
import android.net.wifi.WifiManager.MulticastLock;
|
||||||
|
|
||||||
|
public class PythonService extends Service implements Runnable {
|
||||||
|
|
||||||
|
// Thread for Python code
|
||||||
|
private Thread pythonThread = null;
|
||||||
|
|
||||||
|
// Python environment variables
|
||||||
|
private String androidPrivate;
|
||||||
|
private String androidArgument;
|
||||||
|
private String pythonName;
|
||||||
|
private String pythonHome;
|
||||||
|
private String pythonPath;
|
||||||
|
private String serviceEntrypoint;
|
||||||
|
// Argument to pass to Python code,
|
||||||
|
private String pythonServiceArgument;
|
||||||
|
|
||||||
|
|
||||||
|
public static PythonService mService = null;
|
||||||
|
private Intent startIntent = null;
|
||||||
|
|
||||||
|
private boolean autoRestartService = false;
|
||||||
|
|
||||||
|
public void setAutoRestartService(boolean restart) {
|
||||||
|
autoRestartService = restart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int startType() {
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent arg0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
if (pythonThread != null) {
|
||||||
|
Log.v("python service", "service exists, do not start again");
|
||||||
|
return startType();
|
||||||
|
}
|
||||||
|
//intent is null if OS restarts a STICKY service
|
||||||
|
if (intent == null) {
|
||||||
|
Context context = getApplicationContext();
|
||||||
|
intent = getThisDefaultIntent(context, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
startIntent = intent;
|
||||||
|
Bundle extras = intent.getExtras();
|
||||||
|
androidPrivate = extras.getString("androidPrivate");
|
||||||
|
androidArgument = extras.getString("androidArgument");
|
||||||
|
serviceEntrypoint = extras.getString("serviceEntrypoint");
|
||||||
|
pythonName = extras.getString("pythonName");
|
||||||
|
pythonHome = extras.getString("pythonHome");
|
||||||
|
pythonPath = extras.getString("pythonPath");
|
||||||
|
boolean serviceStartAsForeground = (
|
||||||
|
extras.getString("serviceStartAsForeground").equals("true")
|
||||||
|
);
|
||||||
|
pythonServiceArgument = extras.getString("pythonServiceArgument");
|
||||||
|
pythonThread = new Thread(this);
|
||||||
|
pythonThread.start();
|
||||||
|
|
||||||
|
if (serviceStartAsForeground) {
|
||||||
|
doStartForeground(extras);
|
||||||
|
}
|
||||||
|
|
||||||
|
return startType();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getServiceId() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Intent getThisDefaultIntent(Context ctx, String pythonServiceArgument) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doStartForeground(Bundle extras) {
|
||||||
|
String serviceTitle = extras.getString("serviceTitle");
|
||||||
|
String serviceDescription = extras.getString("serviceDescription");
|
||||||
|
Notification notification;
|
||||||
|
Context context = getApplicationContext();
|
||||||
|
Intent contextIntent = new Intent(context, PythonActivity.class);
|
||||||
|
PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||||
|
notification = new Notification(
|
||||||
|
context.getApplicationInfo().icon, serviceTitle, System.currentTimeMillis());
|
||||||
|
try {
|
||||||
|
// prevent using NotificationCompat, this saves 100kb on apk
|
||||||
|
Method func = notification.getClass().getMethod(
|
||||||
|
"setLatestEventInfo", Context.class, CharSequence.class,
|
||||||
|
CharSequence.class, PendingIntent.class);
|
||||||
|
func.invoke(notification, context, serviceTitle, serviceDescription, pIntent);
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException |
|
||||||
|
IllegalArgumentException | InvocationTargetException e) {
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// for android 8+ we need to create our own channel
|
||||||
|
// https://stackoverflow.com/questions/47531742/startforeground-fail-after-upgrade-to-android-8-1
|
||||||
|
String NOTIFICATION_CHANNEL_ID = "org.kivy.p4a"; //TODO: make this configurable
|
||||||
|
String channelName = "Background Service"; //TODO: make this configurable
|
||||||
|
NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName,
|
||||||
|
NotificationManager.IMPORTANCE_NONE);
|
||||||
|
|
||||||
|
chan.setLightColor(Color.BLUE);
|
||||||
|
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
|
||||||
|
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
manager.createNotificationChannel(chan);
|
||||||
|
|
||||||
|
Notification.Builder builder = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID);
|
||||||
|
builder.setContentTitle("Sideband");
|
||||||
|
builder.setContentText("Reticulum Running");
|
||||||
|
builder.setContentIntent(pIntent);
|
||||||
|
|
||||||
|
Bitmap icon_bitmap = BitmapFactory.decodeFile("/data/user/0/io.unsigned.sideband/files/app/assets/notification_icon.png");
|
||||||
|
Icon service_icon = Icon.createWithBitmap(icon_bitmap);
|
||||||
|
// builder.setSmallIcon(context.getApplicationInfo().icon);
|
||||||
|
builder.setSmallIcon(service_icon);
|
||||||
|
notification = builder.build();
|
||||||
|
|
||||||
|
// Log.v("python service", "Testing WM locks...");
|
||||||
|
// WifiManager wm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
|
||||||
|
// MulticastLock ml = wm.createMulticastLock("sometag");
|
||||||
|
// Log.v("python service", "WM locks done");
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
startForeground(getServiceId(), notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
pythonThread = null;
|
||||||
|
if (autoRestartService && startIntent != null) {
|
||||||
|
Log.v("python service", "service restart requested");
|
||||||
|
startService(startIntent);
|
||||||
|
}
|
||||||
|
Process.killProcess(Process.myPid());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the task gracefully when killed.
|
||||||
|
* Calling stopSelf() will trigger a onDestroy() call from the system.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onTaskRemoved(Intent rootIntent) {
|
||||||
|
super.onTaskRemoved(rootIntent);
|
||||||
|
//sticky servcie runtime/restart is managed by the OS. leave it running when app is closed
|
||||||
|
if (startType() != START_STICKY) {
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(){
|
||||||
|
String app_root = getFilesDir().getAbsolutePath() + "/app";
|
||||||
|
File app_root_file = new File(app_root);
|
||||||
|
PythonUtil.loadLibraries(app_root_file,
|
||||||
|
new File(getApplicationInfo().nativeLibraryDir));
|
||||||
|
this.mService = this;
|
||||||
|
nativeStart(
|
||||||
|
androidPrivate, androidArgument,
|
||||||
|
serviceEntrypoint, pythonName,
|
||||||
|
pythonHome, pythonPath,
|
||||||
|
pythonServiceArgument);
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Native part
|
||||||
|
public static native void nativeStart(
|
||||||
|
String androidPrivate, String androidArgument,
|
||||||
|
String serviceEntrypoint, String pythonName,
|
||||||
|
String pythonHome, String pythonPath,
|
||||||
|
String pythonServiceArgument);
|
||||||
|
}
|
|
@ -1,14 +1,44 @@
|
||||||
import time
|
import time
|
||||||
|
import RNS
|
||||||
|
from sideband.core import SidebandCore
|
||||||
|
from os import environ
|
||||||
|
from jnius import autoclass, cast
|
||||||
|
|
||||||
|
Context = autoclass('android.content.Context')
|
||||||
|
|
||||||
class sidebandservice():
|
class sidebandservice():
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self.argument = environ.get('PYTHON_SERVICE_ARGUMENT', '')
|
||||||
|
self.multicast_lock = None
|
||||||
|
self.wake_lock = None
|
||||||
|
|
||||||
|
self.service = autoclass('org.kivy.android.PythonService').mService
|
||||||
|
self.app_context = self.service.getApplication().getApplicationContext()
|
||||||
|
self.wifi_manager = self.app_context.getSystemService(Context.WIFI_SERVICE)
|
||||||
|
# The returned instance is an android.net.wifi.WifiManager
|
||||||
|
|
||||||
print("Sideband Service created")
|
print("Sideband Service created")
|
||||||
|
self.take_locks()
|
||||||
self.run()
|
self.run()
|
||||||
|
|
||||||
|
def take_locks(self):
|
||||||
|
if self.multicast_lock == None:
|
||||||
|
self.multicast_lock = self.wifi_manager.createMulticastLock("sideband_service")
|
||||||
|
|
||||||
|
if not self.multicast_lock.isHeld():
|
||||||
|
RNS.log("Taking multicast lock")
|
||||||
|
self.multicast_lock.acquire()
|
||||||
|
RNS.log("Took lock")
|
||||||
|
|
||||||
|
|
||||||
|
def release_locks():
|
||||||
|
if not self.multicast_lock == None and self.multicast_lock.isHeld():
|
||||||
|
self.multicast_lock.release()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
while True:
|
while True:
|
||||||
print("Service ping")
|
print("Service ping")
|
||||||
time.sleep(3)
|
time.sleep(5)
|
||||||
|
|
||||||
sbs = sidebandservice()
|
sbs = sidebandservice()
|
|
@ -705,7 +705,8 @@ class SidebandCore():
|
||||||
self.lxmf_destination.announce()
|
self.lxmf_destination.announce()
|
||||||
|
|
||||||
def __start_jobs_immediate(self):
|
def __start_jobs_immediate(self):
|
||||||
self.reticulum = RNS.Reticulum(configdir=self.rns_configdir)
|
# TODO: Reset loglevel
|
||||||
|
self.reticulum = RNS.Reticulum(configdir=self.rns_configdir, loglevel=7)
|
||||||
RNS.log("Reticulum started, activating LXMF...")
|
RNS.log("Reticulum started, activating LXMF...")
|
||||||
|
|
||||||
if RNS.vendor.platformutils.get_platform() == "android":
|
if RNS.vendor.platformutils.get_platform() == "android":
|
||||||
|
|
Loading…
Reference in New Issue